From 9a0166a81aad1443f6ee746667fdfa2ff32d33bc Mon Sep 17 00:00:00 2001 From: Helene Arnesen <86235924+helehar@users.noreply.github.com> Date: Mon, 9 Dec 2024 13:52:33 +0100 Subject: [PATCH] Chore: added functionality for send and avvis sykmelding chore: Added functionality for send and avvis sykmelding to this app. Korrigere sykmelding endpoint will save the updated object in the DB as well as smregistrering until the functionality is moved. --- build.gradle.kts | 8 +- naiserator-dev.yaml | 14 + naiserator-prod.yaml | 14 + src/main/kotlin/no/nav/sykdig/Log.kt | 8 + .../no/nav/sykdig/config/M2MRestTemplate.kt | 14 + .../sykdig/config/SecurityConfiguration.kt | 1 + .../sykdig/config/kafka/AivenKafkaConfig.kt | 2 +- .../no/nav/sykdig/db/OppgaveRepository.kt | 10 +- .../sykdig/digitalisering/OppgaveMapping.kt | 3 +- .../digitalisering/SykDigOppgaveService.kt | 112 ++-- .../UtenlandskOppgaveService.kt | 6 +- .../digitalisering/dokarkiv/DokarkivClient.kt | 169 +++++- .../dokarkiv/OppdaterJournalpostRequest.kt | 2 +- .../digitalisering/exceptions/Exceptions.kt | 7 +- .../exceptions/GlobalExceptionHandler.kt | 6 + .../{sykmelding => felles}/Sykmelding.kt | 2 +- .../ferdigstilling/FerdigstillingService.kt | 86 ++- .../ferdigstilling/GosysService.kt | 4 +- .../mapping/SykmeldingMapper.kt | 42 +- .../oppgave/GetOppgaveResponse.kt | 25 +- .../ferdigstilling/oppgave/OppgaveClient.kt | 125 ++++- .../{Oppgavestatus.kt => OppgaveStatus.kt} | 2 +- .../oppgave/PatchOppgaveRequest.kt | 14 +- .../helsenett/SykmelderService.kt | 10 +- .../helsenett/client/HelsenettClient.kt | 2 - .../helsenett/client/SmtssClient.kt | 54 ++ .../papirsykmelding/NasjonalCommonService.kt | 172 ++++++ .../papirsykmelding/NasjonalOppgaveService.kt | 383 +++++++++++-- .../NasjonalSykmeldingService.kt | 248 +++++++++ .../api/NasjonalOppgaveController.kt | 81 ++- .../papirsykmelding/api/RegelClient.kt | 40 ++ .../api/SmregistreringClient.kt | 5 +- .../api/model/FerdigstillRegistrering.kt | 22 + .../api/model/PapirSykmelding.kt | 18 +- .../papirsykmelding/api/model/Sykmelding.kt | 200 ------- .../api/model/ValidationRules.kt | 183 ++++++ .../api/model/WhitelistedRuleHit.kt | 18 + .../db/NasjonalOppgaveRepository.kt | 5 +- .../db/NasjonalSykmeldingRepository.kt | 4 +- .../db/model/CustomPgConverters.kt | 87 +++ .../db/model/NasjonalManuellOppgaveDAO.kt | 34 +- .../db/model/NasjonalSykmeldingDAO.kt | 4 +- .../papirsykmelding/db/model/Utfall.kt | 7 + .../saf/SafJournalpostService.kt | 8 + .../sykmelding/ReceivedSykmelding.kt | 3 +- .../sykmelding/service/JournalpostService.kt | 40 +- .../OppgaveSecurityService.kt | 140 +++-- .../no/nav/sykdig/model/SDSykmelding.kt | 16 +- .../kotlin/no/nav/sykdig/utils/DateTime.kt | 18 + .../NasjonalSykmeldingFellesformatMapper.kt | 523 ++++++++++++++++++ .../sykdig/utils/NasjonalSykmeldingMapper.kt | 200 +++++++ .../kotlin/no/nav/sykdig/utils/RuleInfoExt.kt | 12 + .../nav/sykdig/utils/{Titel.kt => Title.kt} | 13 +- src/main/resources/application.yaml | 22 +- .../migration/V14__create_nasjonal_tables.sql | 22 +- .../V15__add_id_nasjonal_sykmelding.sql | 14 - .../V16__create_sykmelding_index.sql | 1 - .../kotlin/no/nav/sykdig/IntegrationTest.kt | 4 + .../SykDigOppgaveServiceTest.kt | 2 +- .../UtenlandskOppgaveServiceTest.kt | 4 +- .../FerdigstillingServiceTest.kt | 24 +- .../helsenett/SykmelderServiceTest.kt | 13 +- .../NasjonalOppgaveRepositoryTest.kt | 16 +- .../NasjonalOppgaveServiceTest.kt | 187 ++++++- .../NasjonalSykmeldingRepositoryTest.kt | 136 +++++ .../sykdig/saf/SafJournalpostServiceTest.kt | 69 ++- src/test/resources/application.yaml | 17 +- 67 files changed, 3155 insertions(+), 602 deletions(-) rename src/main/kotlin/no/nav/sykdig/digitalisering/{sykmelding => felles}/Sykmelding.kt (99%) rename src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/{Oppgavestatus.kt => OppgaveStatus.kt} (84%) create mode 100644 src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/client/SmtssClient.kt create mode 100644 src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalCommonService.kt create mode 100644 src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalSykmeldingService.kt create mode 100644 src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/RegelClient.kt create mode 100644 src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/FerdigstillRegistrering.kt delete mode 100644 src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/Sykmelding.kt create mode 100644 src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/ValidationRules.kt create mode 100644 src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/WhitelistedRuleHit.kt create mode 100644 src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/CustomPgConverters.kt create mode 100644 src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/Utfall.kt create mode 100644 src/main/kotlin/no/nav/sykdig/utils/DateTime.kt create mode 100644 src/main/kotlin/no/nav/sykdig/utils/NasjonalSykmeldingFellesformatMapper.kt create mode 100644 src/main/kotlin/no/nav/sykdig/utils/NasjonalSykmeldingMapper.kt create mode 100644 src/main/kotlin/no/nav/sykdig/utils/RuleInfoExt.kt rename src/main/kotlin/no/nav/sykdig/utils/{Titel.kt => Title.kt} (80%) delete mode 100644 src/main/resources/db/migration/V15__add_id_nasjonal_sykmelding.sql delete mode 100644 src/main/resources/db/migration/V16__create_sykmelding_index.sql create mode 100644 src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalSykmeldingRepositoryTest.kt diff --git a/build.gradle.kts b/build.gradle.kts index 5060588d..17b42fcb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -48,16 +48,18 @@ val prometheusVersion = "0.16.0" val mockkVersion = "1.13.10" val kluentVersion = "1.73" val coroutinesVersion = "1.8.1" +val coroutineReactorVersion = "1.9.0" val hibernateVersion = "6.2.6.Final" val jacksonDatatypeJsr310Version = "2.18.0" - +val mockitoKotlinVersion = "5.4.0" dependencies { implementation(platform("com.netflix.graphql.dgs:graphql-dgs-platform-dependencies:$graphqlDgsPlatformDependenciesVersion")) implementation("com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter") implementation("com.netflix.graphql.dgs:graphql-dgs-extended-scalars") implementation("com.graphql-java:graphql-java:$graphqlVersion") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-slf4j:$coroutinesVersion") - implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$coroutineReactorVersion") +// implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("com.fasterxml.jackson.module:jackson-module-jaxb-annotations") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonDatatypeJsr310Version") @@ -109,11 +111,13 @@ dependencies { testImplementation("org.testcontainers:kafka:$testContainersVersion") testImplementation("io.mockk:mockk:$mockkVersion") testImplementation("org.amshove.kluent:kluent:$kluentVersion") + testImplementation("org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion") constraints { testImplementation("org.apache.commons:commons-compress:$commonsCompressVersion") { because("overstyrer sårbar dependency fra com.opentable.components:otj-pg-embedded") } } + testImplementation(kotlin("test")) } tasks { diff --git a/naiserator-dev.yaml b/naiserator-dev.yaml index 4302ef27..81a46a36 100644 --- a/naiserator-dev.yaml +++ b/naiserator-dev.yaml @@ -83,6 +83,12 @@ spec: - application: syfohelsenettproxy namespace: teamsykmelding cluster: dev-gcp + - application: smtss + namespace: teamsykmelding + cluster: dev-gcp + - application: syfosmpapirregler + namespace: teamsykmelding + cluster: dev-gcp external: - host: kodeverk-api.nav.no - host: oppgave-q1.dev-fss-pub.nais.io @@ -127,3 +133,11 @@ spec: value: http://syfohelsenettproxy - name: HELSENETT_CLIENT_ID value: dev-gcp.teamsykmelding.syfohelsenettproxy + - name: SMTSS_URL + value: http://smtss + - name: SMTSS_CLIENT_ID + value: dev-gcp.teamsykmelding.smtss + - name: SYFOSMPAPIRREGLER_URL + value: http://syfosmpapirregler + - name: SYFOSMPAPIRREGLER_CLIENT_ID + value: dev-gcp.teamsykmelding.syfosmpapirregler diff --git a/naiserator-prod.yaml b/naiserator-prod.yaml index 5b315bcf..0abe3f56 100644 --- a/naiserator-prod.yaml +++ b/naiserator-prod.yaml @@ -83,6 +83,12 @@ spec: - application: syfohelsenettproxy namespace: teamsykmelding cluster: prod-gcp + - application: smtss + namespace: teamsykmelding + cluster: prod-gcp + - application: syfosmpapirregler + namespace: teamsykmelding + cluster: prod-gcp external: - host: saf.prod-fss-pub.nais.io - host: pdl-api.prod-fss-pub.nais.io @@ -124,3 +130,11 @@ spec: value: http://syfohelsenettproxy - name: HELSENETT_CLIENT_ID value: prod-gcp.teamsykmelding.syfohelsenettproxy + - name: SMTSS_URL + value: http://smtss + - name: SMTSS_CLIENT_ID + value: prod-gcp.teamsykmelding.smtss + - name: SYFOSMPAPIRREGLER_URL + value: http://syfosmpapirregler + - name: SYFOSMPAPIRREGLER_CLIENT_ID + value: prod-gcp.teamsykmelding.syfosmpapirregler diff --git a/src/main/kotlin/no/nav/sykdig/Log.kt b/src/main/kotlin/no/nav/sykdig/Log.kt index 59d59965..4cc0b3de 100644 --- a/src/main/kotlin/no/nav/sykdig/Log.kt +++ b/src/main/kotlin/no/nav/sykdig/Log.kt @@ -18,3 +18,11 @@ inline fun T.securelog(): Logger { inline fun T.auditlog(): Logger { return LoggerFactory.getLogger("auditLogger") } + +data class LoggingMeta( + val mottakId: String, + val journalpostId: String?, + val dokumentInfoId: String?, + val msgId: String, + val sykmeldingId: String, +) diff --git a/src/main/kotlin/no/nav/sykdig/config/M2MRestTemplate.kt b/src/main/kotlin/no/nav/sykdig/config/M2MRestTemplate.kt index 8c39a3e0..15f8a0f3 100644 --- a/src/main/kotlin/no/nav/sykdig/config/M2MRestTemplate.kt +++ b/src/main/kotlin/no/nav/sykdig/config/M2MRestTemplate.kt @@ -41,6 +41,20 @@ class M2MRestTemplate( .build() } + @Bean + fun smtssM2mRestTemplate(): RestTemplate { + return restTemplateBuilder + .additionalInterceptors(bearerTokenInterceptor("smtss-m2m")) + .build() + } + + @Bean + fun regeltM2mRestTemplate(): RestTemplate { + return restTemplateBuilder + .additionalInterceptors(bearerTokenInterceptor("regel-m2m")) + .build() + } + private fun bearerTokenInterceptor(type: String): ClientHttpRequestInterceptor { return ClientHttpRequestInterceptor { request: HttpRequest, body: ByteArray, execution: ClientHttpRequestExecution -> val token = m2mTokenService.getM2MToken(type) diff --git a/src/main/kotlin/no/nav/sykdig/config/SecurityConfiguration.kt b/src/main/kotlin/no/nav/sykdig/config/SecurityConfiguration.kt index b5448b1a..2558ca62 100644 --- a/src/main/kotlin/no/nav/sykdig/config/SecurityConfiguration.kt +++ b/src/main/kotlin/no/nav/sykdig/config/SecurityConfiguration.kt @@ -5,6 +5,7 @@ import org.springframework.context.annotation.Configuration import org.springframework.http.HttpMethod import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity import org.springframework.security.config.annotation.web.builders.HttpSecurity +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity import org.springframework.security.web.SecurityFilterChain @Configuration diff --git a/src/main/kotlin/no/nav/sykdig/config/kafka/AivenKafkaConfig.kt b/src/main/kotlin/no/nav/sykdig/config/kafka/AivenKafkaConfig.kt index 18e76510..75261349 100644 --- a/src/main/kotlin/no/nav/sykdig/config/kafka/AivenKafkaConfig.kt +++ b/src/main/kotlin/no/nav/sykdig/config/kafka/AivenKafkaConfig.kt @@ -105,4 +105,4 @@ class AivenKafkaConfig( } const val SYK_DIG_OPPGAVE_TOPIC = "teamsykmelding.syk-dig-oppgave" -const val OK_SYKMLEDING_TOPIC = "teamsykmelding.ok-sykmelding" +const val OK_SYKMELDING_TOPIC = "teamsykmelding.ok-sykmelding" diff --git a/src/main/kotlin/no/nav/sykdig/db/OppgaveRepository.kt b/src/main/kotlin/no/nav/sykdig/db/OppgaveRepository.kt index 6aafc0da..6c04da24 100644 --- a/src/main/kotlin/no/nav/sykdig/db/OppgaveRepository.kt +++ b/src/main/kotlin/no/nav/sykdig/db/OppgaveRepository.kt @@ -3,11 +3,11 @@ package no.nav.sykdig.db import com.fasterxml.jackson.module.kotlin.readValue import no.nav.sykdig.applog import no.nav.sykdig.digitalisering.model.RegisterOppgaveValues -import no.nav.sykdig.digitalisering.sykmelding.AktivitetIkkeMulig -import no.nav.sykdig.digitalisering.sykmelding.Diagnose -import no.nav.sykdig.digitalisering.sykmelding.Gradert -import no.nav.sykdig.digitalisering.sykmelding.MedisinskVurdering -import no.nav.sykdig.digitalisering.sykmelding.Periode +import no.nav.sykdig.digitalisering.felles.AktivitetIkkeMulig +import no.nav.sykdig.digitalisering.felles.Diagnose +import no.nav.sykdig.digitalisering.felles.Gradert +import no.nav.sykdig.digitalisering.felles.MedisinskVurdering +import no.nav.sykdig.digitalisering.felles.Periode import no.nav.sykdig.digitalisering.sykmelding.UtenlandskSykmelding import no.nav.sykdig.generated.types.Avvisingsgrunn import no.nav.sykdig.generated.types.PeriodeInput diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/OppgaveMapping.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/OppgaveMapping.kt index cd2ceb08..bd9a4e73 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/OppgaveMapping.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/OppgaveMapping.kt @@ -1,8 +1,7 @@ package no.nav.sykdig.digitalisering import no.nav.sykdig.digitalisering.pdl.toFormattedNameString -import no.nav.sykdig.digitalisering.sykmelding.Periode -import no.nav.sykdig.generated.DgsConstants.QUERY.DigitalisertSykmelding +import no.nav.sykdig.digitalisering.felles.Periode import no.nav.sykdig.generated.types.Bostedsadresse import no.nav.sykdig.generated.types.DiagnoseValue import no.nav.sykdig.generated.types.Digitaliseringsoppgave diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/SykDigOppgaveService.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/SykDigOppgaveService.kt index 78ea5d70..ea09de8b 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/SykDigOppgaveService.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/SykDigOppgaveService.kt @@ -13,9 +13,8 @@ import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.OppgaveClient import no.nav.sykdig.digitalisering.model.FerdistilltRegisterOppgaveValues import no.nav.sykdig.digitalisering.model.RegisterOppgaveValues import no.nav.sykdig.digitalisering.pdl.Person +import no.nav.sykdig.digitalisering.saf.graphql.DokumentInfo import no.nav.sykdig.digitalisering.saf.graphql.SafJournalpost -import no.nav.sykdig.digitalisering.tilgangskontroll.OppgaveSecurityService -import no.nav.sykdig.digitalisering.tilgangskontroll.getNavEmail import no.nav.sykdig.generated.types.Avvisingsgrunn import no.nav.sykdig.model.DokumentDbModel import no.nav.sykdig.model.OppgaveDbModel @@ -35,59 +34,28 @@ class SykDigOppgaveService( private val log = applog() private val securelog = securelog() - private fun createOppgave(oppgaveId: String, fnr: String, journalpostId: String, journalpost: SafJournalpost, dokumentInfoId: String, source: String = "syk-dig"): OppgaveDbModel { - val dokumenter = journalpost.dokumenter.map { - val oppdatertTittel = if (it.tittel == "Utenlandsk sykmelding") { - "Digitalisert utenlandsk sykmelding" - } else { - it.tittel ?: "Mangler Tittel" - } - DokumentDbModel(it.dokumentInfoId, oppdatertTittel) - } - - return OppgaveDbModel( - oppgaveId = oppgaveId, - fnr = fnr, - journalpostId = journalpostId, - dokumentInfoId = dokumentInfoId, - dokumenter = dokumenter, - opprettet = OffsetDateTime.now(ZoneOffset.UTC), - ferdigstilt = null, - tilbakeTilGosys = false, - avvisingsgrunn = null, - sykmeldingId = UUID.randomUUID(), - type = UTLAND, - sykmelding = null, - endretAv = getNavEmail(), - timestamp = OffsetDateTime.now(ZoneOffset.UTC), - source = source, - ) - } fun opprettOgLagreOppgave( journalpost: SafJournalpost, journalpostId: String, fnr: String, aktoerId: String, + navEpost: String, ): String { - val response = - oppgaveClient.opprettOppgave( - journalpostId = journalpostId, - aktoerId = aktoerId, - ) + val opprettetOppgave = oppgaveClient.opprettOppgave( + journalpostId = journalpostId, + aktoerId = aktoerId, + ) + val oppgaveId = opprettetOppgave.id.toString() - val oppgaveId = response.id.toString() - val dokumentInfoId = journalpost.dokumenter.first().dokumentInfoId - val tittel = journalpost.tittel.lowercase().contains("egenerklæring") - securelog.info("is egenarklaring: $tittel journalpostId: $journalpostId") - val oppgave = createOppgave( + val oppgave = buildOppgaveModel( oppgaveId = oppgaveId, - fnr = fnr, - journalpostId = journalpostId, journalpost = journalpost, - dokumentInfoId = dokumentInfoId, - source = if (journalpost.kanal == "NAV_NO" || tittel) "navno" else if (journalpost.kanal == "RINA") "rina" else "syk-dig" + journalpostId = journalpostId, + fnr = fnr, + navEpost = navEpost, ) + oppgaveRepository.lagreOppgave(oppgave) log.info("Oppgave med id $oppgaveId lagret") return oppgaveId @@ -211,7 +179,7 @@ class SykDigOppgaveService( ) { val sykmelding = oppgaveRepository.getLastSykmelding(oppgave.oppgaveId) oppgaveRepository.ferdigstillAvvistOppgave(oppgave, navEpost, sykmelding, avvisningsgrunn) - ferdigstillingService.ferdigstillAvvistJournalpost( + ferdigstillingService.ferdigstillUtenlandskAvvistJournalpost( enhet = enhetId, oppgave = oppgave, sykmeldt = sykmeldt, @@ -220,7 +188,7 @@ class SykDigOppgaveService( } @Transactional - fun ferdigstillOppgave( + fun ferdigstillUtenlandskAvvistOppgave( oppgave: OppgaveDbModel, navEpost: String, values: FerdistilltRegisterOppgaveValues, @@ -230,7 +198,7 @@ class SykDigOppgaveService( val sykmelding = toSykmelding(oppgave, values) oppgaveRepository.updateOppgave(oppgave, sykmelding, navEpost, true) - ferdigstillingService.ferdigstill( + ferdigstillingService.ferdigstillUtenlandskOppgave( enhet = enhetId, oppgave = oppgave, sykmeldt = sykmeldt, @@ -247,6 +215,56 @@ class SykDigOppgaveService( ferdigstillingService.sendUpdatedSykmelding(oppgave, sykmeldt, navEmail, values) } + private fun buildOppgaveModel( + oppgaveId: String, + journalpost: SafJournalpost, + journalpostId: String, + fnr: String, + navEpost: String, + ): OppgaveDbModel { + val dokumentInfoId = journalpost.dokumenter.first().dokumentInfoId + val dokumenter = buildDokumenter(journalpost.dokumenter) + val source = determineSource(journalpost) + + return OppgaveDbModel( + oppgaveId = oppgaveId, + fnr = fnr, + journalpostId = journalpostId, + dokumentInfoId = dokumentInfoId, + dokumenter = dokumenter, + opprettet = OffsetDateTime.now(ZoneOffset.UTC), + ferdigstilt = null, + tilbakeTilGosys = false, + avvisingsgrunn = null, + sykmeldingId = UUID.randomUUID(), + type = UTLAND, + sykmelding = null, + endretAv = navEpost, + timestamp = OffsetDateTime.now(ZoneOffset.UTC), + source = source, + ) + } + + private fun buildDokumenter(dokumenter: List): List { + return dokumenter.map { + val oppdatertTittel = if (it.tittel == "Utenlandsk sykmelding") { + "Digitalisert utenlandsk sykmelding" + } else { + it.tittel ?: "Mangler Tittel" + } + DokumentDbModel(it.dokumentInfoId, oppdatertTittel) + } + } + + private fun determineSource(journalpost: SafJournalpost): String { + val isEgenerklaering = journalpost.tittel.lowercase().contains("egenerklæring") + return when { + journalpost.kanal == "NAV_NO" || isEgenerklaering -> "navno" + journalpost.kanal == "RINA" -> "rina" + else -> "syk-dig" + } + } + companion object { private const val UTLAND = "UTLAND" } diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/UtenlandskOppgaveService.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/UtenlandskOppgaveService.kt index da0313fe..009ec32d 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/UtenlandskOppgaveService.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/UtenlandskOppgaveService.kt @@ -7,13 +7,9 @@ import no.nav.sykdig.digitalisering.model.FerdistilltRegisterOppgaveValues import no.nav.sykdig.digitalisering.model.RegisterOppgaveValues import no.nav.sykdig.digitalisering.pdl.PersonService import no.nav.sykdig.digitalisering.regelvalidering.RegelvalideringService -import no.nav.sykdig.digitalisering.sykmelding.Sykmelding import no.nav.sykdig.generated.types.Avvisingsgrunn -import no.nav.sykdig.generated.types.DigitaliseringsoppgaveStatus -import no.nav.sykdig.generated.types.DigitaliseringsoppgaveStatusEnum import no.nav.sykdig.generated.types.OppdatertSykmeldingStatus import no.nav.sykdig.generated.types.OppdatertSykmeldingStatusEnum -import no.nav.sykdig.generated.types.SykmeldingUnderArbeidValues import no.nav.sykdig.metrics.MetricRegister import no.nav.sykdig.model.OppgaveDbModel import org.springframework.stereotype.Service @@ -83,7 +79,7 @@ class UtenlandskOppgaveService( throw ClientException(valideringsresultat.joinToString()) } - sykDigOppgaveService.ferdigstillOppgave(oppgave, navEpost, values, enhetId, sykmeldt) + sykDigOppgaveService.ferdigstillUtenlandskAvvistOppgave(oppgave, navEpost, values, enhetId, sykmeldt) metricRegister.ferdigstiltOppgave.increment() } diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/dokarkiv/DokarkivClient.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/dokarkiv/DokarkivClient.kt index 3b07287b..d92a7e36 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/dokarkiv/DokarkivClient.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/dokarkiv/DokarkivClient.kt @@ -1,14 +1,18 @@ package no.nav.sykdig.digitalisering.dokarkiv import com.fasterxml.jackson.module.kotlin.readValue +import no.nav.sykdig.LoggingMeta import no.nav.sykdig.applog import no.nav.sykdig.digitalisering.exceptions.IkkeTilgangException +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Sykmelder import no.nav.sykdig.digitalisering.saf.graphql.AvsenderMottaker import no.nav.sykdig.digitalisering.saf.graphql.AvsenderMottakerIdType -import no.nav.sykdig.digitalisering.sykmelding.Periode +import no.nav.sykdig.digitalisering.felles.Periode +import no.nav.sykdig.digitalisering.sykmelding.ReceivedSykmelding import no.nav.sykdig.objectMapper import no.nav.sykdig.securelog import no.nav.sykdig.utils.createTitle +import no.nav.sykdig.utils.createTitleNasjonal import no.nav.sykdig.utils.createTitleNavNo import no.nav.sykdig.utils.createTitleRina import org.springframework.beans.factory.annotation.Value @@ -16,6 +20,7 @@ import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders import org.springframework.http.HttpMethod import org.springframework.http.MediaType +import org.springframework.http.ResponseEntity import org.springframework.retry.annotation.Retryable import org.springframework.stereotype.Component import org.springframework.web.client.HttpClientErrorException @@ -52,11 +57,11 @@ class DokarkivClient( ) } - fun oppdaterOgFerdigstillJournalpost( + fun oppdaterOgFerdigstillUtenlandskJournalpost( landAlpha3: String?, fnr: String, enhet: String, - dokumentinfoId: String, + dokumentinfoId: String?, journalpostId: String, sykmeldingId: String, perioder: List?, @@ -65,7 +70,7 @@ class DokarkivClient( orginalAvsenderMottaker: AvsenderMottaker?, sykmeldtNavn: String?, ) { - oppdaterJournalpost( + oppdaterUtenlandskJournalpost( landAlpha3 = landAlpha3, fnr = fnr, dokumentinfoId = dokumentinfoId, @@ -85,10 +90,10 @@ class DokarkivClient( } @Retryable - private fun oppdaterJournalpost( + private fun oppdaterUtenlandskJournalpost( landAlpha3: String?, fnr: String, - dokumentinfoId: String, + dokumentinfoId: String?, journalpostId: String, sykmeldingId: String, perioder: List?, @@ -97,13 +102,8 @@ class DokarkivClient( orginalAvsenderMottaker: AvsenderMottaker?, sykmeldtNavn: String?, ) { - val headers = HttpHeaders() - headers.contentType = MediaType.APPLICATION_JSON - headers.accept = listOf(MediaType.APPLICATION_JSON) - headers["Nav-Callid"] = sykmeldingId - val body = - createOppdaterJournalpostRequest( + createOppdaterUtenlandskJournalpostRequest( landAlpha3, fnr, dokumentinfoId, @@ -113,16 +113,30 @@ class DokarkivClient( orginalAvsenderMottaker, sykmeldtNavn, ) + oppdaterJournalpostRequest(body, sykmeldingId, journalpostId) + } + + @Retryable + private fun oppdaterJournalpostRequest( + oppdaterJournalpostRequest: OppdaterJournalpostRequest, + sykmeldingId: String, + journalpostId: String, + ): ResponseEntity { + val headers = HttpHeaders() + headers.contentType = MediaType.APPLICATION_JSON + headers.accept = listOf(MediaType.APPLICATION_JSON) + headers["Nav-Callid"] = sykmeldingId try { - securelog.info("createOppdaterJournalpostRequest: ${objectMapper.writeValueAsString(body)}") - dokarkivRestTemplate.exchange( + securelog.info("createOppdaterUtenlandskJournalpostRequest: ${objectMapper.writeValueAsString(oppdaterJournalpostRequest)}") + val response = dokarkivRestTemplate.exchange( "$url/$journalpostId", HttpMethod.PUT, - HttpEntity(body, headers), + HttpEntity(oppdaterJournalpostRequest, headers), String::class.java, ) log.info("Oppdatert journalpost $journalpostId for sykmelding $sykmeldingId") + return response } catch (e: HttpClientErrorException) { if (e.statusCode.value() == 401 || e.statusCode.value() == 403) { log.warn("Veileder har ikke tilgang til å oppdatere journalpostId $journalpostId: ${e.message}") @@ -148,10 +162,10 @@ class DokarkivClient( } } - private fun createOppdaterJournalpostRequest( + private fun createOppdaterUtenlandskJournalpostRequest( landAlpha3: String?, fnr: String, - dokumentinfoId: String, + dokumentinfoId: String?, perioder: List?, source: String, avvisningsGrunn: String?, @@ -324,7 +338,7 @@ class DokarkivClient( enhet: String, journalpostId: String, sykmeldingId: String, - ) { + ): ResponseEntity { val headers = HttpHeaders() headers.contentType = MediaType.APPLICATION_JSON headers.accept = listOf(MediaType.APPLICATION_JSON) @@ -335,13 +349,14 @@ class DokarkivClient( journalfoerendeEnhet = enhet, ) try { - dokarkivRestTemplate.exchange( + val response = dokarkivRestTemplate.exchange( "$url/$journalpostId/ferdigstill", HttpMethod.PATCH, HttpEntity(body, headers), String::class.java, ) log.info("Ferdigstilt journalpost $journalpostId for sykmelding $sykmeldingId") + return response } catch (e: HttpClientErrorException) { if (e.statusCode.value() == 401 || e.statusCode.value() == 403) { log.warn("Veileder har ikke tilgang til å ferdigstille journalpostId $journalpostId: ${e.message}") @@ -366,6 +381,121 @@ class DokarkivClient( throw e } } + + fun oppdaterOgFerdigstillNasjonalJournalpost( + journalpostId: String, + dokumentInfoId: String? = null, + pasientFnr: String, + sykmeldingId: String, + sykmelder: Sykmelder, + loggingMeta: LoggingMeta, + navEnhet: String, + avvist: Boolean, + receivedSykmelding: ReceivedSykmelding, + ): String? { + val oppdaterJournalpostRequest = createOppdaterJournalpostNasjonalRequest(dokumentInfoId, pasientFnr, sykmelder, avvist, receivedSykmelding) + oppdaterJournalpostRequest(oppdaterJournalpostRequest, sykmeldingId, journalpostId) + + return ferdigstillJournalpost( + enhet = navEnhet, + journalpostId = journalpostId, + sykmeldingId = sykmeldingId + ).body + } + + private fun createOppdaterJournalpostNasjonalRequest( + dokumentInfoId: String?, + pasientFnr: String, + sykmelder: Sykmelder, + avvist: Boolean, + receivedSykmelding: ReceivedSykmelding, + ): OppdaterJournalpostRequest { + val oppdaterJournalpostRequest = OppdaterJournalpostRequest( + avsenderMottaker = AvsenderMottakerRequest( + id = padHpr(sykmelder.hprNummer), + navn = finnNavn(sykmelder), + land = null, + idType = null, + ), + bruker = DokBruker(id = pasientFnr), + sak = Sak(), + tittel = createTitleNasjonal(receivedSykmelding.sykmelding.perioder, avvist), + dokumenter = if (dokumentInfoId != null) { + listOf( + DokumentInfo( + dokumentInfoId = dokumentInfoId, + tittel = createTitleNasjonal(receivedSykmelding.sykmelding.perioder, avvist), + ), + ) + } else { + null + }, + ) + return oppdaterJournalpostRequest + } + + fun oppdaterOgFerdigstillNasjonalJournalpost( + journalpostId: String, + dokumentInfoId: String? = null, + pasientFnr: String, + sykmeldingId: String, + sykmelder: Sykmelder, + loggingMeta: LoggingMeta, + navEnhet: String, + avvist: Boolean, + perioder: List, + ): String? { + val oppdaterJournalpostRequest = createOppdaterJournalpostNasjonalRequest(dokumentInfoId, pasientFnr, sykmelder, avvist, perioder) + oppdaterJournalpostRequest(oppdaterJournalpostRequest, sykmeldingId, journalpostId) + + return ferdigstillJournalpost( + enhet = navEnhet, + journalpostId = journalpostId, + sykmeldingId = sykmeldingId + ).body + } + + private fun createOppdaterJournalpostNasjonalRequest( + dokumentInfoId: String?, + pasientFnr: String, + sykmelder: Sykmelder, + avvist: Boolean, + perioder: List, + ): OppdaterJournalpostRequest { + val oppdaterJournalpostRequest = OppdaterJournalpostRequest( + avsenderMottaker = AvsenderMottakerRequest( + id = padHpr(sykmelder.hprNummer), + navn = finnNavn(sykmelder), + land = null, + idType = null, + ), + bruker = DokBruker(id = pasientFnr), + sak = Sak(), + tittel = createTitleNasjonal(perioder, avvist), + dokumenter = if (dokumentInfoId != null) { + listOf( + DokumentInfo( + dokumentInfoId = dokumentInfoId, + tittel = createTitleNasjonal(perioder, avvist), + ), + ) + } else { + null + }, + ) + return oppdaterJournalpostRequest + } + private fun padHpr(hprnummer: String): String { + if (hprnummer.length < 9) { + securelog.info("padder hpr: $hprnummer") + return hprnummer.padStart(9, '0') + } + return hprnummer + } +} + +fun finnNavn(sykmelder: Sykmelder): String { + return "${sykmelder.fornavn} ${sykmelder.etternavn}" } data class Country( @@ -374,3 +504,4 @@ data class Country( val alpha3: String, val name: String, ) + diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/dokarkiv/OppdaterJournalpostRequest.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/dokarkiv/OppdaterJournalpostRequest.kt index dd2d03bd..3c716f5f 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/dokarkiv/OppdaterJournalpostRequest.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/dokarkiv/OppdaterJournalpostRequest.kt @@ -43,6 +43,6 @@ data class Sak( ) data class DokumentInfo( - val dokumentInfoId: String, + val dokumentInfoId: String?, val tittel: String?, ) diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/exceptions/Exceptions.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/exceptions/Exceptions.kt index 6b12e315..5be6aa49 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/exceptions/Exceptions.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/exceptions/Exceptions.kt @@ -1,6 +1,7 @@ package no.nav.sykdig.digitalisering.exceptions import graphql.GraphQLException +import no.nav.sykdig.digitalisering.sykmelding.ValidationResult class IkkeTilgangException(override val message: String) : GraphQLException(message) @@ -12,4 +13,8 @@ class NoOppgaveException(override val message: String) : RuntimeException(messag class SykmelderNotFoundException(message: String) : RuntimeException(message) -class UnauthorizedException(message: String) : Exception(message) \ No newline at end of file +class MissingJournalpostException(message: String) : RuntimeException(message) + +class UnauthorizedException(message: String) : Exception(message) + +class ValidationException(val validationResult: ValidationResult) : Exception() diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/exceptions/GlobalExceptionHandler.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/exceptions/GlobalExceptionHandler.kt index c25666c8..a69d0b88 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/exceptions/GlobalExceptionHandler.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/exceptions/GlobalExceptionHandler.kt @@ -56,6 +56,12 @@ class GlobalExceptionHandler { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Sykmelder not found") } + @ExceptionHandler(MissingJournalpostException::class) + fun handleMissingJournalpostException(e: MissingJournalpostException): ResponseEntity { + log.error("Journalpost is missing: ${e.message}", e) + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Journalpost is missing") + } + @ExceptionHandler(UnauthorizedException::class) fun handleUnAuthorizedException(e: UnauthorizedException): ResponseEntity { log.warn("Caught UnauthorizedException ${e.message}", e) diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/sykmelding/Sykmelding.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/felles/Sykmelding.kt similarity index 99% rename from src/main/kotlin/no/nav/sykdig/digitalisering/sykmelding/Sykmelding.kt rename to src/main/kotlin/no/nav/sykdig/digitalisering/felles/Sykmelding.kt index 12ac32a6..29b739a1 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/sykmelding/Sykmelding.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/felles/Sykmelding.kt @@ -1,4 +1,4 @@ -package no.nav.sykdig.digitalisering.sykmelding +package no.nav.sykdig.digitalisering.felles import java.time.LocalDate import java.time.LocalDateTime diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/FerdigstillingService.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/FerdigstillingService.kt index 0ba1c816..142fd780 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/FerdigstillingService.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/FerdigstillingService.kt @@ -1,12 +1,16 @@ package no.nav.sykdig.digitalisering.ferdigstilling +import no.nav.sykdig.LoggingMeta import no.nav.sykdig.applog -import no.nav.sykdig.config.kafka.OK_SYKMLEDING_TOPIC +import no.nav.sykdig.config.kafka.OK_SYKMELDING_TOPIC import no.nav.sykdig.digitalisering.dokarkiv.DokarkivClient import no.nav.sykdig.digitalisering.dokument.DocumentService +import no.nav.sykdig.digitalisering.felles.Periode import no.nav.sykdig.digitalisering.ferdigstilling.mapping.mapToReceivedSykmelding import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.OppgaveClient +import no.nav.sykdig.digitalisering.helsenett.SykmelderService import no.nav.sykdig.digitalisering.model.FerdistilltRegisterOppgaveValues +import no.nav.sykdig.digitalisering.papirsykmelding.db.model.NasjonalManuellOppgaveDAO import no.nav.sykdig.digitalisering.pdl.Person import no.nav.sykdig.digitalisering.pdl.toFormattedNameString import no.nav.sykdig.digitalisering.saf.SafJournalpostGraphQlClient @@ -15,6 +19,7 @@ import no.nav.sykdig.model.OppgaveDbModel import no.nav.sykdig.objectMapper import no.nav.sykdig.securelog import no.nav.sykdig.utils.createTitle +import no.nav.sykdig.utils.createTitleNasjonal import no.nav.sykdig.utils.createTitleNavNo import no.nav.sykdig.utils.createTitleRina import org.apache.kafka.clients.producer.KafkaProducer @@ -28,11 +33,12 @@ class FerdigstillingService( private val oppgaveClient: OppgaveClient, private val sykmeldingOKProducer: KafkaProducer, private val dokumentService: DocumentService, + private val sykmelderService: SykmelderService, ) { val log = applog() val securelog = securelog() - fun ferdigstill( + fun ferdigstillUtenlandskOppgave( enhet: String, oppgave: OppgaveDbModel, sykmeldt: Person, @@ -52,10 +58,10 @@ class FerdigstillingService( securelog.info("journalpostid ${oppgave.journalpostId} ble hentet: ${objectMapper.writeValueAsString(journalpost)}") if (safJournalpostGraphQlClient.erFerdigstilt(journalpost)) { log.info("Journalpost med id ${oppgave.journalpostId} er allerede ferdigstilt, sykmeldingId ${oppgave.sykmeldingId}") - updateAvvistTitle(oppgave, receivedSykmelding) + updateUtenlandskDocumentTitle(oppgave, receivedSykmelding, isAvvist = true) } else { val hentAvvsenderMottar = safJournalpostGraphQlClient.getAvsenderMottar(journalpost) - dokarkivClient.oppdaterOgFerdigstillJournalpost( + dokarkivClient.oppdaterOgFerdigstillUtenlandskJournalpost( landAlpha3 = validatedValues.skrevetLand, fnr = sykmeldt.fnr, enhet = enhet, @@ -71,15 +77,15 @@ class FerdigstillingService( } oppgaveClient.ferdigstillOppgave(oppgaveId = oppgave.oppgaveId, sykmeldingId = oppgave.sykmeldingId.toString()) - updateTitle(oppgave, receivedSykmelding) + updateUtenlandskDocumentTitle(oppgave, receivedSykmelding) try { sykmeldingOKProducer.send( - ProducerRecord(OK_SYKMLEDING_TOPIC, receivedSykmelding.sykmelding.id, receivedSykmelding), + ProducerRecord(OK_SYKMELDING_TOPIC, receivedSykmelding.sykmelding.id, receivedSykmelding), ).get() log.info( "Sykmelding sendt to kafka topic {} sykmelding id {}", - OK_SYKMLEDING_TOPIC, + OK_SYKMELDING_TOPIC, receivedSykmelding.sykmelding.id, ) } catch (exception: Exception) { @@ -88,26 +94,12 @@ class FerdigstillingService( } } - private fun updateTitle( - oppgave: OppgaveDbModel, - receivedSykmelding: ReceivedSykmelding - ) { - updateDocumentTitle(oppgave, receivedSykmelding) - } - - private fun updateAvvistTitle( - oppgave: OppgaveDbModel, - receivedSykmelding: ReceivedSykmelding - ) { - updateDocumentTitle(oppgave, receivedSykmelding, isAvvist = true) - } - - private fun updateDocumentTitle( + private fun updateUtenlandskDocumentTitle( oppgave: OppgaveDbModel, receivedSykmelding: ReceivedSykmelding, isAvvist: Boolean = false ) { - securelog.info("documents: ${oppgave.dokumenter.map { it.tittel }} source: ${oppgave.source} sykmeldignId: ${receivedSykmelding.sykmelding.id} ") + securelog.info("documents: ${oppgave.dokumenter.map { it.tittel }} source: ${oppgave.source} sykmeldingId: ${receivedSykmelding.sykmelding.id} ") val dokument = when { isAvvist -> oppgave.dokumenter.firstOrNull { it.tittel.lowercase().startsWith("avvist") } @@ -141,11 +133,11 @@ class FerdigstillingService( } } - fun ferdigstillAvvistJournalpost( + fun ferdigstillUtenlandskAvvistJournalpost( enhet: String, oppgave: OppgaveDbModel, sykmeldt: Person, - avvisningsGrunn: String, + avvisningsGrunn: String?, ) { requireNotNull(oppgave.dokumentInfoId) { "DokumentInfoId må være satt for å kunne ferdigstille oppgave" } val journalpost = safJournalpostGraphQlClient.getJournalpost(oppgave.journalpostId) @@ -155,7 +147,7 @@ class FerdigstillingService( log.info("Journalpost med id ${oppgave.journalpostId} er allerede ferdigstilt, sykmeldingId ${oppgave.sykmeldingId}") } else { val hentAvsenderMottar = safJournalpostGraphQlClient.getAvsenderMottar(journalpost) - dokarkivClient.oppdaterOgFerdigstillJournalpost( + dokarkivClient.oppdaterOgFerdigstillUtenlandskJournalpost( landAlpha3 = null, fnr = sykmeldt.fnr, enhet = enhet, @@ -171,6 +163,44 @@ class FerdigstillingService( } } + fun ferdigstillNasjonalAvvistJournalpost( + enhet: String, + oppgave: NasjonalManuellOppgaveDAO, + sykmeldt: Person, + avvisningsGrunn: String?, + loggingMeta: LoggingMeta, + ) { + requireNotNull(oppgave.dokumentInfoId) { "DokumentInfoId må være satt for å kunne ferdigstille oppgave" } + val journalpost = safJournalpostGraphQlClient.getJournalpost(oppgave.journalpostId) + securelog.info("journalpostid ${oppgave.journalpostId} ble hentet: ${objectMapper.writeValueAsString(journalpost)}") + + if (safJournalpostGraphQlClient.erFerdigstilt(journalpost)) { + log.info("Journalpost med id ${oppgave.journalpostId} er allerede ferdigstilt, sykmeldingId ${oppgave.sykmeldingId}") + } + else { + log.info("Ferdigstiller journalpost med id ${oppgave.journalpostId}, dokumentInfoId ${oppgave.dokumentInfoId}, sykmeldingId ${oppgave.sykmeldingId} og oppgaveId ${oppgave.oppgaveId}") + dokarkivClient.oppdaterOgFerdigstillNasjonalJournalpost( + journalpostId = oppgave.journalpostId, + dokumentInfoId = oppgave.dokumentInfoId, + pasientFnr = oppgave.fnr!!, + sykmeldingId = oppgave.sykmeldingId, + sykmelder = sykmelderService.getSykmelder(oppgave.papirSmRegistrering.behandler?.hpr!!, "callId"), + loggingMeta = loggingMeta, + navEnhet = enhet, + avvist = true, + perioder = oppgave.papirSmRegistrering.perioder ?: emptyList(), + ) + + oppgaveClient.ferdigstillOppgave(oppgave.oppgaveId.toString(), oppgave.sykmeldingId) + + dokarkivClient.updateDocument( + journalpostid = oppgave.journalpostId, + documentId = oppgave.dokumentInfoId, + tittel = createTitleNasjonal(oppgave.papirSmRegistrering.perioder, true), + ) + } + } + fun sendUpdatedSykmelding(oppgave: OppgaveDbModel, sykmeldt: Person, navEmail: String, values: FerdistilltRegisterOppgaveValues) { val receivedSykmelding = mapToReceivedSykmelding( @@ -181,9 +211,9 @@ class FerdigstillingService( opprettet = oppgave.opprettet.toLocalDateTime(), ) sykmeldingOKProducer.send( - ProducerRecord(OK_SYKMLEDING_TOPIC, receivedSykmelding.sykmelding.id, receivedSykmelding), + ProducerRecord(OK_SYKMELDING_TOPIC, receivedSykmelding.sykmelding.id, receivedSykmelding), ).get() log.info("sendt oppdatert sykmelding med id ${receivedSykmelding.sykmelding.id}") - updateTitle(oppgave, receivedSykmelding) + updateUtenlandskDocumentTitle(oppgave, receivedSykmelding) } } diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/GosysService.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/GosysService.kt index ff967aef..d889f717 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/GosysService.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/GosysService.kt @@ -1,7 +1,7 @@ package no.nav.sykdig.digitalisering.ferdigstilling import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.OppgaveClient -import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.Oppgavestatus +import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.OppgaveStatus import org.springframework.stereotype.Component @Component @@ -39,7 +39,7 @@ class GosysService( oppgaveId, sykmeldingId, oppgave.versjon, - Oppgavestatus.FERDIGSTILT, + OppgaveStatus.FERDIGSTILT, "FS22", veilederNavIdent, beskrivelse, diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/mapping/SykmeldingMapper.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/mapping/SykmeldingMapper.kt index af1310af..4ef1d012 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/mapping/SykmeldingMapper.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/mapping/SykmeldingMapper.kt @@ -5,27 +5,27 @@ import no.nav.helse.sm2013.ArsakType import no.nav.helse.sm2013.CS import no.nav.helse.sm2013.CV import no.nav.helse.sm2013.HelseOpplysningerArbeidsuforhet -import no.nav.sykdig.digitalisering.sykmelding.Adresse -import no.nav.sykdig.digitalisering.sykmelding.AktivitetIkkeMulig -import no.nav.sykdig.digitalisering.sykmelding.AnnenFraverGrunn -import no.nav.sykdig.digitalisering.sykmelding.AnnenFraversArsak -import no.nav.sykdig.digitalisering.sykmelding.Arbeidsgiver -import no.nav.sykdig.digitalisering.sykmelding.ArbeidsrelatertArsak -import no.nav.sykdig.digitalisering.sykmelding.ArbeidsrelatertArsakType -import no.nav.sykdig.digitalisering.sykmelding.AvsenderSystem -import no.nav.sykdig.digitalisering.sykmelding.Behandler -import no.nav.sykdig.digitalisering.sykmelding.Diagnose -import no.nav.sykdig.digitalisering.sykmelding.Gradert -import no.nav.sykdig.digitalisering.sykmelding.HarArbeidsgiver -import no.nav.sykdig.digitalisering.sykmelding.KontaktMedPasient -import no.nav.sykdig.digitalisering.sykmelding.MedisinskArsak -import no.nav.sykdig.digitalisering.sykmelding.MedisinskArsakType -import no.nav.sykdig.digitalisering.sykmelding.MedisinskVurdering -import no.nav.sykdig.digitalisering.sykmelding.MeldingTilNAV -import no.nav.sykdig.digitalisering.sykmelding.Periode -import no.nav.sykdig.digitalisering.sykmelding.SporsmalSvar -import no.nav.sykdig.digitalisering.sykmelding.SvarRestriksjon -import no.nav.sykdig.digitalisering.sykmelding.Sykmelding +import no.nav.sykdig.digitalisering.felles.Adresse +import no.nav.sykdig.digitalisering.felles.AktivitetIkkeMulig +import no.nav.sykdig.digitalisering.felles.AnnenFraverGrunn +import no.nav.sykdig.digitalisering.felles.AnnenFraversArsak +import no.nav.sykdig.digitalisering.felles.Arbeidsgiver +import no.nav.sykdig.digitalisering.felles.ArbeidsrelatertArsak +import no.nav.sykdig.digitalisering.felles.ArbeidsrelatertArsakType +import no.nav.sykdig.digitalisering.felles.AvsenderSystem +import no.nav.sykdig.digitalisering.felles.Behandler +import no.nav.sykdig.digitalisering.felles.Diagnose +import no.nav.sykdig.digitalisering.felles.Gradert +import no.nav.sykdig.digitalisering.felles.HarArbeidsgiver +import no.nav.sykdig.digitalisering.felles.KontaktMedPasient +import no.nav.sykdig.digitalisering.felles.MedisinskArsak +import no.nav.sykdig.digitalisering.felles.MedisinskArsakType +import no.nav.sykdig.digitalisering.felles.MedisinskVurdering +import no.nav.sykdig.digitalisering.felles.MeldingTilNAV +import no.nav.sykdig.digitalisering.felles.Periode +import no.nav.sykdig.digitalisering.felles.SporsmalSvar +import no.nav.sykdig.digitalisering.felles.SvarRestriksjon +import no.nav.sykdig.digitalisering.felles.Sykmelding import java.time.LocalDateTime fun HelseOpplysningerArbeidsuforhet.toSykmelding( diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/GetOppgaveResponse.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/GetOppgaveResponse.kt index 2d83fead..eefbffd9 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/GetOppgaveResponse.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/GetOppgaveResponse.kt @@ -4,7 +4,7 @@ import java.time.LocalDate data class GetOppgaveResponse( val versjon: Int, - val status: Oppgavestatus, + val status: OppgaveStatus, val id: Int? = null, val oppgavetype: String, val journalpostId: String?, @@ -23,10 +23,31 @@ data class GetOppgaveResponse( val behandlingstema: String?, ) +data class NasjonalOppgaveResponse( + val id: Int? = null, + val versjon: Int? = null, + val tildeltEnhetsnr: String? = null, + val opprettetAvEnhetsnr: String? = null, + val aktoerId: String? = null, + val journalpostId: String? = null, + val behandlesAvApplikasjon: String? = null, + val saksreferanse: String? = null, + val tilordnetRessurs: String? = null, + val beskrivelse: String? = null, + val tema: String? = null, + val oppgavetype: String, + val behandlingstype: String? = null, + val aktivDato: LocalDate, + val fristFerdigstillelse: LocalDate? = null, + val prioritet: String, + val status: String? = null, + val mappeId: Int? = null, +) + data class AllOppgaveResponse( val versjon: Int, val aktoerId: String, - val status: Oppgavestatus, + val status: OppgaveStatus, val aktivDato: LocalDate? = null, val id: Int? = null, val tema: String? = null, diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/OppgaveClient.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/OppgaveClient.kt index 63d7fb8c..75b849f6 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/OppgaveClient.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/OppgaveClient.kt @@ -1,19 +1,19 @@ package no.nav.sykdig.digitalisering.ferdigstilling.oppgave +import net.logstash.logback.argument.StructuredArguments import net.logstash.logback.argument.StructuredArguments.kv +import no.nav.sykdig.LoggingMeta import no.nav.sykdig.applog import no.nav.sykdig.digitalisering.exceptions.IkkeTilgangException import no.nav.sykdig.digitalisering.exceptions.NoOppgaveException import no.nav.sykdig.digitalisering.getFristForFerdigstillingAvOppgave +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.FerdigstillRegistrering import no.nav.sykdig.digitalisering.saf.graphql.SafJournalpost import no.nav.sykdig.digitalisering.saf.graphql.TEMA_SYKMELDING import no.nav.sykdig.objectMapper import no.nav.sykdig.securelog import org.springframework.beans.factory.annotation.Value -import org.springframework.http.HttpEntity -import org.springframework.http.HttpHeaders -import org.springframework.http.HttpMethod -import org.springframework.http.MediaType +import org.springframework.http.* import org.springframework.retry.annotation.Retryable import org.springframework.stereotype.Component import org.springframework.web.client.HttpClientErrorException @@ -48,7 +48,7 @@ class OppgaveClient( sykmeldingId: String, ) { val oppgave = getOppgave(oppgaveId, sykmeldingId) - if (oppgave.status == Oppgavestatus.FERDIGSTILT || oppgave.status == Oppgavestatus.FEILREGISTRERT) { + if (oppgave.status == OppgaveStatus.FERDIGSTILT || oppgave.status == OppgaveStatus.FEILREGISTRERT) { log.info("Oppgave med id $oppgaveId er allerede ferdigstilt") } else { ferdigstillOppgave(oppgaveId, sykmeldingId, oppgave.versjon) @@ -56,6 +56,37 @@ class OppgaveClient( } } + fun ferdigstillNasjonalOppgave( + oppgaveId: String, + sykmeldingId: String, + ferdigstillRegistrering: FerdigstillRegistrering, + loggingMeta: LoggingMeta + ) { + val oppgave = getNasjonalOppgave(oppgaveId, sykmeldingId) + if (oppgave.status == OppgaveStatus.FERDIGSTILT.name || oppgave.status == OppgaveStatus.FEILREGISTRERT.name) { + log.info("Oppgave med id $oppgaveId er allerede ferdigstilt") + return + } + val ferdigstillOppgave = PatchFerdigstillNasjonalOppgaveRequest( + id = oppgaveId.toInt(), + versjon = oppgave.versjon ?: throw RuntimeException( + "Fant ikke versjon for oppgave ${oppgave.id}, sykmeldingId ${ferdigstillRegistrering.sykmeldingId}" + ), + status = OppgaveStatus.FERDIGSTILT, + tilordnetRessurs = ferdigstillRegistrering.veileder.veilederIdent, + tildeltEnhetsnr = ferdigstillRegistrering.navEnhet, + mappeId = null, + beskrivelse = oppgave.beskrivelse, + ) + log.info( + "Ferdigstiller oppgave med {}, {}", + StructuredArguments.keyValue("oppgaveId", oppgaveId), + StructuredArguments.fields(loggingMeta), + ) + ferdigstillNasjonalOppgave(oppgaveId, sykmeldingId, ferdigstillOppgave) + log.info("Ferdigstilt oppgave med id $oppgaveId i Oppgave") + } + @Retryable fun getOppgaveM2m( oppgaveId: String, @@ -131,6 +162,43 @@ class OppgaveClient( } } + @Retryable + fun getNasjonalOppgave( + oppgaveId: String, + sykmeldingId: String, + ): NasjonalOppgaveResponse { + val headers = HttpHeaders() + headers.contentType = MediaType.APPLICATION_JSON + headers["X-Correlation-ID"] = sykmeldingId + try { + val response = + oppgaveRestTemplate.exchange( + "$url/$oppgaveId", + HttpMethod.GET, + HttpEntity(headers), + NasjonalOppgaveResponse::class.java, + ) + return response.body ?: throw NoOppgaveException("Fant ikke oppgaver på journalpostId $oppgaveId") + } catch (e: HttpClientErrorException) { + if (e.statusCode.value() == 401 || e.statusCode.value() == 403) { + log.warn("Veileder har ikke tilgang til oppgaveId $oppgaveId: ${e.message}") + throw IkkeTilgangException("Veileder har ikke tilgang til oppgave") + } else { + log.error( + "HttpClientErrorException med responskode ${e.statusCode.value()} fra Oppgave: ${e.message}", + e, + ) + throw e + } + } catch (e: HttpServerErrorException) { + log.error("HttpServerErrorException med responskode ${e.statusCode.value()} fra Oppgave: ${e.message}", e) + throw e + } catch (e: Exception) { + log.error("Other Exception fra Oppgave: ${e.message}", e) + throw e + } + } + @Retryable fun getOppgaver( journalpostId: String, @@ -191,7 +259,7 @@ class OppgaveClient( val body = PatchFerdigStillOppgaveRequest( versjon = oppgaveVersjon, - status = Oppgavestatus.FERDIGSTILT, + status = OppgaveStatus.FERDIGSTILT, id = oppgaveId.toInt(), ) try { @@ -224,6 +292,47 @@ class OppgaveClient( } } + @Retryable + private fun ferdigstillNasjonalOppgave( + oppgaveId: String, + sykmeldingId: String, + nasjonalFerdigstillOppgave: PatchFerdigstillNasjonalOppgaveRequest + ): ResponseEntity { + val headers = HttpHeaders() + headers.contentType = MediaType.APPLICATION_JSON + headers["X-Correlation-ID"] = sykmeldingId + + try { + val response = oppgaveRestTemplate.exchange( + "$url/$oppgaveId", + HttpMethod.PATCH, + HttpEntity(nasjonalFerdigstillOppgave, headers), + String::class.java, + ) + log.info("Ferdigstilt nasjonal oppgave $oppgaveId for sykmelding $sykmeldingId") + return response + } catch (e: HttpClientErrorException) { + if (e.statusCode.value() == 401 || e.statusCode.value() == 403) { + log.warn("Veileder har ikke tilgang til å ferdigstille oppgaveId $oppgaveId: ${e.message}") + throw IkkeTilgangException("Veileder har ikke tilgang til oppgave") + } else { + log.error( + "HttpClientErrorException for oppgaveId $oppgaveId med responskode " + + "${e.statusCode.value()} fra Oppgave ved ferdigstilling: ${e.message}", + e, + ) + throw e + } + } catch (e: HttpServerErrorException) { + log.error( + "HttpServerErrorException for oppgaveId $oppgaveId med responskode " + + "${e.statusCode.value()} fra Oppgave ved ferdigstilling: ${e.message}", + e, + ) + throw e + } + } + @Retryable fun ferdigstillJournalføringsoppgave( oppgaveId: Int, @@ -237,7 +346,7 @@ class OppgaveClient( val body = PatchFerdigStillOppgaveRequest( versjon = oppgaveVersjon, - status = Oppgavestatus.FERDIGSTILT, + status = OppgaveStatus.FERDIGSTILT, id = oppgaveId, ) try { @@ -312,7 +421,7 @@ class OppgaveClient( oppgaveId: String, sykmeldingId: String, oppgaveVersjon: Int, - oppgaveStatus: Oppgavestatus, + oppgaveStatus: OppgaveStatus, oppgaveBehandlesAvApplikasjon: String, oppgaveTilordnetRessurs: String, beskrivelse: String?, diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/Oppgavestatus.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/OppgaveStatus.kt similarity index 84% rename from src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/Oppgavestatus.kt rename to src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/OppgaveStatus.kt index f217fd69..5bfd3792 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/Oppgavestatus.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/OppgaveStatus.kt @@ -1,6 +1,6 @@ package no.nav.sykdig.digitalisering.ferdigstilling.oppgave -enum class Oppgavestatus { +enum class OppgaveStatus { OPPRETTET, AAPNET, UNDER_BEHANDLING, diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/PatchOppgaveRequest.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/PatchOppgaveRequest.kt index 79cb6e9d..d238697b 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/PatchOppgaveRequest.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/oppgave/PatchOppgaveRequest.kt @@ -2,14 +2,24 @@ package no.nav.sykdig.digitalisering.ferdigstilling.oppgave data class PatchFerdigStillOppgaveRequest( val versjon: Int, - val status: Oppgavestatus, + val status: OppgaveStatus, val id: Int, val mappeId: Int? = null, ) +data class PatchFerdigstillNasjonalOppgaveRequest( + val id: Int, + val versjon: Int, + val status: OppgaveStatus, + val tilordnetRessurs: String, + val tildeltEnhetsnr: String, + val mappeId: Int?, + val beskrivelse: String? = null, +) + data class PatchToGosysOppgaveRequest( val versjon: Int, - val status: Oppgavestatus, + val status: OppgaveStatus, val id: Int, val mappeId: Int? = null, val behandlesAvApplikasjon: String?, diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/SykmelderService.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/SykmelderService.kt index e46d0377..b89c9b8c 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/SykmelderService.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/SykmelderService.kt @@ -1,8 +1,11 @@ package no.nav.sykdig.digitalisering.helsenett +import no.nav.sykdig.LoggingMeta import no.nav.sykdig.applog import no.nav.sykdig.digitalisering.exceptions.SykmelderNotFoundException import no.nav.sykdig.digitalisering.helsenett.client.HelsenettClient +import no.nav.sykdig.digitalisering.helsenett.client.SmtssClient +import no.nav.sykdig.digitalisering.papirsykmelding.api.RegelClient import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Godkjenning import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Kode import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Sykmelder @@ -14,11 +17,12 @@ import org.springframework.stereotype.Service class SykmelderService( private val helsenettClient: HelsenettClient, private val personService: PersonService, + private val smtssClient: SmtssClient, ) { val log = applog() val securelog = securelog() - suspend fun getSykmelder( + fun getSykmelder( hprNummer: String, callId: String, ): Sykmelder { @@ -47,6 +51,10 @@ class SykmelderService( ) } + suspend fun getTssIdInfotrygd(samhandlerFnr: String, samhandlerOrgName: String, loggingMeta: LoggingMeta, sykmeldingId: String): String { + return smtssClient.findBestTssInfotrygd(samhandlerFnr, samhandlerOrgName, loggingMeta, sykmeldingId) + } + fun changeHelsepersonellkategoriVerdiFromFAToFA1( godkjenninger: List, ): List { diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/client/HelsenettClient.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/client/HelsenettClient.kt index 99a25c7f..8e91cec1 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/client/HelsenettClient.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/client/HelsenettClient.kt @@ -2,7 +2,6 @@ package no.nav.sykdig.digitalisering.helsenett.client import no.nav.sykdig.applog import no.nav.sykdig.digitalisering.exceptions.SykmelderNotFoundException -import no.nav.sykdig.digitalisering.exceptions.UnauthorizedException import no.nav.sykdig.digitalisering.helsenett.Behandler import no.nav.sykdig.securelog import org.springframework.beans.factory.annotation.Value @@ -13,7 +12,6 @@ import org.springframework.http.HttpStatus import org.springframework.stereotype.Component import org.springframework.web.client.HttpClientErrorException import org.springframework.web.client.RestTemplate -import java.io.IOException @Component class HelsenettClient( diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/client/SmtssClient.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/client/SmtssClient.kt new file mode 100644 index 00000000..0353ddf4 --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/helsenett/client/SmtssClient.kt @@ -0,0 +1,54 @@ +package no.nav.sykdig.digitalisering.helsenett.client + +import net.logstash.logback.argument.StructuredArguments +import no.nav.sykdig.LoggingMeta +import no.nav.sykdig.applog +import no.nav.sykdig.digitalisering.exceptions.SykmelderNotFoundException +import no.nav.sykdig.securelog +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.stereotype.Component +import org.springframework.web.client.RestTemplate + +@Component +class SmtssClient( + @Value("\${smtss.url}") private val smtssUrl: String, + private val smtssM2mRestTemplate: RestTemplate + ) { + + val log = applog() + val securelog = securelog() + + suspend fun findBestTssInfotrygd( + samhandlerFnr: String, + samhandlerOrgName: String, + loggingMeta: LoggingMeta, + sykmeldingId: String, + ): String { + + val headers = HttpHeaders() + headers["samhandlerFnr"] = samhandlerFnr + headers["samhandlerOrgName"] = samhandlerOrgName + headers["requestId"] = sykmeldingId + + val response = + smtssM2mRestTemplate.exchange( + "$smtssUrl/api/v1/samhandler/infotrygd", + HttpMethod.GET, + HttpEntity(headers), + String::class.java, + ) + if (response.statusCode.is2xxSuccessful) return response.body + log.info( + "smtss responded with an error code {} for {}", + response.statusCode, + StructuredArguments.fields(loggingMeta), + ) + throw SykmelderNotFoundException("Samhandlerpraksis ikke funnet for samhandlerOrgname ${samhandlerOrgName}") + } + + + +} \ No newline at end of file diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalCommonService.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalCommonService.kt new file mode 100644 index 00000000..1d373a04 --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalCommonService.kt @@ -0,0 +1,172 @@ +package no.nav.sykdig.digitalisering.papirsykmelding + +import no.nav.helse.msgHead.XMLMsgHead +import no.nav.syfo.service.toSykmelding +import no.nav.sykdig.LoggingMeta +import no.nav.sykdig.applog +import no.nav.sykdig.digitalisering.felles.AvsenderSystem +import no.nav.sykdig.digitalisering.felles.KontaktMedPasient +import no.nav.sykdig.digitalisering.felles.Sykmelding +import no.nav.sykdig.digitalisering.ferdigstilling.mapping.extractHelseOpplysningerArbeidsuforhet +import no.nav.sykdig.digitalisering.ferdigstilling.mapping.fellesformatMarshaller +import no.nav.sykdig.digitalisering.ferdigstilling.mapping.get +import no.nav.sykdig.digitalisering.ferdigstilling.mapping.toString +import no.nav.sykdig.digitalisering.helsenett.SykmelderService +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Godkjenning +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.SmRegistreringManuell +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Sykmelder +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Veileder +import no.nav.sykdig.digitalisering.papirsykmelding.db.model.NasjonalManuellOppgaveDAO +import no.nav.sykdig.digitalisering.pdl.PersonService +import no.nav.sykdig.digitalisering.sykmelding.Merknad +import no.nav.sykdig.digitalisering.sykmelding.ReceivedSykmelding +import no.nav.sykdig.securelog +import no.nav.sykdig.utils.getLocalDateTime +import no.nav.sykdig.utils.mapsmRegistreringManuelltTilFellesformat +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken +import org.springframework.stereotype.Service +import java.time.LocalDateTime + +@Service +class NasjonalCommonService( + private val sykmelderService: SykmelderService, + private val personService: PersonService, +) { + + val log = applog() + val securelog = securelog() + + + suspend fun createReceivedSykmelding(sykmeldingId: String, oppgave: NasjonalManuellOppgaveDAO, loggingMeta: LoggingMeta, smRegistreringManuell: SmRegistreringManuell, callId: String, sykmelder: Sykmelder): ReceivedSykmelding { + log.info("Henter pasient fra PDL {} ", loggingMeta) + val pasient = + personService.getPerson( + id = smRegistreringManuell.pasientFnr, + callId = callId, + ) + + val tssId = sykmelderService.getTssIdInfotrygd(sykmelder.fnr, "", loggingMeta, sykmeldingId) + + val datoOpprettet = oppgave.datoOpprettet + val journalpostId = oppgave.journalpostId + val fellesformat = + mapsmRegistreringManuelltTilFellesformat( + smRegistreringManuell = smRegistreringManuell, + pdlPasient = pasient, + sykmelder = sykmelder, + sykmeldingId = sykmeldingId, + datoOpprettet = datoOpprettet, + journalpostId = journalpostId, + ) + + val healthInformation = extractHelseOpplysningerArbeidsuforhet(fellesformat) + val msgHead = fellesformat.get() + + val sykmelding = + healthInformation.toSykmelding( + sykmeldingId, + pasient.aktorId, + sykmelder.aktorId, + sykmeldingId, + getLocalDateTime(msgHead.msgInfo.genDate), + ) + + return ReceivedSykmelding( + sykmelding = sykmelding, + personNrPasient = pasient.fnr, + tlfPasient = + healthInformation.pasient.kontaktInfo.firstOrNull()?.teleAddress?.v, + personNrLege = sykmelder.fnr, + navLogId = sykmeldingId, + msgId = sykmeldingId, + legekontorOrgNr = null, + legekontorOrgName = "", + legekontorHerId = null, + legekontorReshId = null, + mottattDato = oppgave.datoOpprettet ?: getLocalDateTime(msgHead.msgInfo.genDate), + rulesetVersion = healthInformation.regelSettVersjon, + fellesformat = fellesformatMarshaller.toString(fellesformat), + tssid = tssId ?: "", + merknader = createMerknad(sykmelding), + partnerreferanse = null, + legeHelsepersonellkategori = + sykmelder.godkjenninger?.getHelsepersonellKategori(), + legeHprNr = sykmelder.hprNummer, + vedlegg = null, + utenlandskSykmelding = null, + ) + + } + + + private fun createMerknad(sykmelding: Sykmelding): List? { + val behandletTidspunkt = sykmelding.behandletTidspunkt.toLocalDate() + val terskel = sykmelding.perioder.map { it.fom }.minOrNull()?.plusDays(7) + return if (behandletTidspunkt != null && terskel != null && behandletTidspunkt > terskel) { + listOf(Merknad("TILBAKEDATERT_PAPIRSYKMELDING", null)) + } else { + null + } + } + + private fun List.getHelsepersonellKategori(): String? = + when { + find { it.helsepersonellkategori?.verdi == "LE" } != null -> "LE" + find { it.helsepersonellkategori?.verdi == "TL" } != null -> "TL" + find { it.helsepersonellkategori?.verdi == "MT" } != null -> "MT" + find { it.helsepersonellkategori?.verdi == "FT" } != null -> "FT" + find { it.helsepersonellkategori?.verdi == "KI" } != null -> "KI" + else -> { + val verdi = firstOrNull()?.helsepersonellkategori?.verdi + log.warn( + "Signerende behandler har ikke en helsepersonellkategori($verdi) vi kjenner igjen", + ) + verdi + } + } + fun getNavIdent(): Veileder { + val authentication = SecurityContextHolder.getContext().authentication as JwtAuthenticationToken + return Veileder(authentication.token.claims["NAVident"].toString()) + } + + fun getNavEmail(): String { + val authentication = SecurityContextHolder.getContext().authentication as JwtAuthenticationToken + return authentication.token.claims["preferred_username"].toString() + } + private fun toSykmelding(sykmeldingId: String, oppgave: NasjonalManuellOppgaveDAO): Sykmelding { + requireNotNull(oppgave.papirSmRegistrering.aktorId) { "PapirSmRegistrering.aktorId er null" } + requireNotNull(oppgave.papirSmRegistrering.medisinskVurdering) { "PapirSmRegistrering.medisinskVurdering er null" } + requireNotNull(oppgave.papirSmRegistrering.arbeidsgiver) { "PapirSmRegistrering.arbeidsgiver er null" } + requireNotNull(oppgave.papirSmRegistrering.behandler) { "PapirSmRegistrering.behandler er null" } + return Sykmelding( + id = sykmeldingId, + msgId = sykmeldingId, + pasientAktoerId = oppgave.papirSmRegistrering.aktorId, + medisinskVurdering = oppgave.papirSmRegistrering.medisinskVurdering, + skjermesForPasient = oppgave.papirSmRegistrering.skjermesForPasient ?: false, + arbeidsgiver = oppgave.papirSmRegistrering.arbeidsgiver, + perioder = oppgave.papirSmRegistrering.perioder ?: emptyList(), + prognose = oppgave.papirSmRegistrering.prognose, + utdypendeOpplysninger = oppgave.papirSmRegistrering.utdypendeOpplysninger ?: emptyMap(), + tiltakArbeidsplassen = oppgave.papirSmRegistrering.tiltakArbeidsplassen, + tiltakNAV = oppgave.papirSmRegistrering.tiltakNAV, + andreTiltak = oppgave.papirSmRegistrering.andreTiltak, + meldingTilNAV = oppgave.papirSmRegistrering.meldingTilNAV, + meldingTilArbeidsgiver = oppgave.papirSmRegistrering.meldingTilArbeidsgiver, + kontaktMedPasient = KontaktMedPasient( + kontaktDato = oppgave.papirSmRegistrering.kontaktMedPasient?.kontaktDato, + begrunnelseIkkeKontakt = oppgave.papirSmRegistrering.kontaktMedPasient?.begrunnelseIkkeKontakt + ), + behandletTidspunkt = LocalDateTime.from(oppgave.papirSmRegistrering.behandletTidspunkt), + behandler = oppgave.papirSmRegistrering.behandler, + avsenderSystem = AvsenderSystem( //TODO + navn = "Navn avsendersystem", + versjon = "0.0" + ), + syketilfelleStartDato = oppgave.papirSmRegistrering.syketilfelleStartDato, + signaturDato = LocalDateTime.from(oppgave.papirSmRegistrering.behandletTidspunkt), + navnFastlege = "Fastlege navn", //TODO + ) + } +} diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveService.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveService.kt index b785bfab..2947999e 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveService.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveService.kt @@ -2,79 +2,350 @@ package no.nav.sykdig.digitalisering.papirsykmelding import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import no.nav.sykdig.LoggingMeta +import no.nav.sykdig.applog +import no.nav.sykdig.digitalisering.exceptions.NoOppgaveException +import no.nav.sykdig.digitalisering.ferdigstilling.FerdigstillingService +import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.OppgaveClient +import no.nav.sykdig.digitalisering.mapAvvisningsgrunn +import no.nav.sykdig.digitalisering.papirsykmelding.api.SmregistreringClient +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.AvvisSykmeldingRequest +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Document +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.FerdigstillRegistrering import no.nav.sykdig.digitalisering.papirsykmelding.api.model.PapirManuellOppgave import no.nav.sykdig.digitalisering.papirsykmelding.api.model.PapirSmRegistering +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.SmRegistreringManuell import no.nav.sykdig.digitalisering.papirsykmelding.db.NasjonalOppgaveRepository import no.nav.sykdig.digitalisering.papirsykmelding.db.model.NasjonalManuellOppgaveDAO +import no.nav.sykdig.digitalisering.papirsykmelding.db.model.Utfall +import no.nav.sykdig.digitalisering.pdl.PersonService +import no.nav.sykdig.generated.types.Avvisingsgrunn +import no.nav.sykdig.securelog +import org.springframework.http.HttpStatus +import org.springframework.http.HttpStatusCode +import org.springframework.http.ResponseEntity import org.springframework.stereotype.Service -import java.util.UUID +import java.time.LocalDateTime +import java.util.* @Service class NasjonalOppgaveService( private val nasjonalOppgaveRepository: NasjonalOppgaveRepository, + private val oppgaveClient: OppgaveClient, + private val personService: PersonService, + private val ferdigstillingService: FerdigstillingService, + private val smregistreringClient: SmregistreringClient, + private val nasjonalCommonService: NasjonalCommonService, ) { - fun lagreOppgave(papirManuellOppgave: PapirManuellOppgave): NasjonalManuellOppgaveDAO { + val log = applog() + val securelog = securelog() + val mapper = jacksonObjectMapper() + + fun lagreOppgave(papirManuellOppgave: PapirManuellOppgave, ferdigstilt: Boolean = false): NasjonalManuellOppgaveDAO { val eksisterendeOppgave = nasjonalOppgaveRepository.findBySykmeldingId(papirManuellOppgave.sykmeldingId) - if (eksisterendeOppgave.isPresent) { - return nasjonalOppgaveRepository.save(mapToDao(papirManuellOppgave, eksisterendeOppgave.get().id)) + securelog.info("Forsøkte å hente eksisterende oppgave med sykmeldingId ${papirManuellOppgave.sykmeldingId} , fant følgende: $eksisterendeOppgave") + + if (eksisterendeOppgave != null) { + log.info("Fant eksisterende oppgave med sykmeldingId ${papirManuellOppgave.sykmeldingId} , oppdaterer oppgave med database id ${eksisterendeOppgave.id}") + return nasjonalOppgaveRepository.save(mapToDao(papirManuellOppgave, eksisterendeOppgave.id, ferdigstilt)) } - return nasjonalOppgaveRepository.save(mapToDao(papirManuellOppgave, null)) + val res = nasjonalOppgaveRepository.save(mapToDao(papirManuellOppgave, null, ferdigstilt)) + log.info("Lagret oppgave med sykmeldingId ${res.sykmeldingId} og med database id ${eksisterendeOppgave?.id}") + securelog.info("Lagret oppgave med sykmeldingId ${res.sykmeldingId} og med database id ${eksisterendeOppgave?.id} og som dette objektet: $res") + return res } - fun mapToDao( - papirManuellOppgave: PapirManuellOppgave, - existingId: UUID?, - ): NasjonalManuellOppgaveDAO { - val mapper = jacksonObjectMapper() - mapper.registerModules(JavaTimeModule()) - - val nasjonalManuellOppgaveDAO = - NasjonalManuellOppgaveDAO( - sykmeldingId = papirManuellOppgave.sykmeldingId, - journalpostId = papirManuellOppgave.papirSmRegistering.journalpostId, - fnr = papirManuellOppgave.fnr, - aktorId = papirManuellOppgave.papirSmRegistering.aktorId, - dokumentInfoId = papirManuellOppgave.papirSmRegistering.dokumentInfoId, - datoOpprettet = papirManuellOppgave.papirSmRegistering.datoOpprettet?.toLocalDateTime(), - oppgaveId = papirManuellOppgave.oppgaveid, - ferdigstilt = false, - papirSmRegistrering = - PapirSmRegistering( - journalpostId = papirManuellOppgave.papirSmRegistering.journalpostId, - oppgaveId = papirManuellOppgave.papirSmRegistering.oppgaveId, - fnr = papirManuellOppgave.papirSmRegistering.fnr, - aktorId = papirManuellOppgave.papirSmRegistering.aktorId, - dokumentInfoId = papirManuellOppgave.papirSmRegistering.dokumentInfoId, - datoOpprettet = papirManuellOppgave.papirSmRegistering.datoOpprettet, - sykmeldingId = papirManuellOppgave.papirSmRegistering.sykmeldingId, - syketilfelleStartDato = papirManuellOppgave.papirSmRegistering.syketilfelleStartDato, - arbeidsgiver = papirManuellOppgave.papirSmRegistering.arbeidsgiver, - medisinskVurdering = papirManuellOppgave.papirSmRegistering.medisinskVurdering, - skjermesForPasient = papirManuellOppgave.papirSmRegistering.skjermesForPasient, - perioder = papirManuellOppgave.papirSmRegistering.perioder, - prognose = papirManuellOppgave.papirSmRegistering.prognose, - utdypendeOpplysninger = papirManuellOppgave.papirSmRegistering.utdypendeOpplysninger, - tiltakNAV = papirManuellOppgave.papirSmRegistering.tiltakNAV, - tiltakArbeidsplassen = papirManuellOppgave.papirSmRegistering.tiltakArbeidsplassen, - andreTiltak = papirManuellOppgave.papirSmRegistering.andreTiltak, - meldingTilNAV = papirManuellOppgave.papirSmRegistering.meldingTilNAV, - meldingTilArbeidsgiver = papirManuellOppgave.papirSmRegistering.meldingTilArbeidsgiver, - kontaktMedPasient = papirManuellOppgave.papirSmRegistering.kontaktMedPasient, - behandletTidspunkt = papirManuellOppgave.papirSmRegistering.behandletTidspunkt, - behandler = papirManuellOppgave.papirSmRegistering.behandler, - ), - utfall = null, - ferdigstiltAv = null, - datoFerdigstilt = null, - avvisningsgrunn = null, - ) + fun oppdaterOppgave(sykmeldingId: String, utfall: String, ferdigstiltAv: String, avvisningsgrunn: String?, smRegistreringManuell: SmRegistreringManuell?): NasjonalManuellOppgaveDAO? { + val existingOppgave = nasjonalOppgaveRepository.findBySykmeldingId(sykmeldingId) + + if (existingOppgave == null) { + log.info("Sykmelding $sykmeldingId not found") + return null + } + + val updatedOppgave = existingOppgave.copy( + utfall = utfall, + ferdigstiltAv = ferdigstiltAv, + avvisningsgrunn = avvisningsgrunn, + datoFerdigstilt = LocalDateTime.now(), + ferdigstilt = true, + papirSmRegistrering = mapToUpdatedPapirSmRegistrering(existingOppgave, smRegistreringManuell) + ) + + securelog.info("Lagret oppgave med sykmeldingId ${updatedOppgave.sykmeldingId} og med database id ${updatedOppgave.id} som dette objektet: $updatedOppgave") + return nasjonalOppgaveRepository.save(updatedOppgave) + } + + private fun mapToUpdatedPapirSmRegistrering(existingOppgave: NasjonalManuellOppgaveDAO, smRegistreringManuell: SmRegistreringManuell?): PapirSmRegistering { + val updatedPapirSmRegistrering = existingOppgave.papirSmRegistrering.copy( + meldingTilArbeidsgiver = smRegistreringManuell?.meldingTilArbeidsgiver + ?: existingOppgave.papirSmRegistrering.meldingTilArbeidsgiver, + medisinskVurdering = smRegistreringManuell?.medisinskVurdering ?: existingOppgave.papirSmRegistrering.medisinskVurdering, + meldingTilNAV = smRegistreringManuell?.meldingTilNAV ?: existingOppgave.papirSmRegistrering.meldingTilNAV, + arbeidsgiver = smRegistreringManuell?.arbeidsgiver ?: existingOppgave.papirSmRegistrering.arbeidsgiver, + kontaktMedPasient = smRegistreringManuell?.kontaktMedPasient ?: existingOppgave.papirSmRegistrering.kontaktMedPasient, + perioder = smRegistreringManuell?.perioder ?: existingOppgave.papirSmRegistrering.perioder, + behandletTidspunkt = smRegistreringManuell?.behandletDato ?: existingOppgave.papirSmRegistrering.behandletTidspunkt, + syketilfelleStartDato = smRegistreringManuell?.syketilfelleStartDato ?: existingOppgave.papirSmRegistrering.syketilfelleStartDato, + behandler = smRegistreringManuell?.behandler ?: existingOppgave.papirSmRegistrering.behandler, + skjermesForPasient = smRegistreringManuell?.skjermesForPasient ?: existingOppgave.papirSmRegistrering.skjermesForPasient, + ) + + securelog.info("Updated papirSmRegistrering: $updatedPapirSmRegistrering to be saved in syk-dig-backend db nasjonal_manuellOppgave") + return updatedPapirSmRegistrering + } + + + private fun findByOppgaveId(oppgaveId: String): NasjonalManuellOppgaveDAO? { + if(!isValidOppgaveId(oppgaveId)) + throw IllegalArgumentException("Invalid oppgaveId does not contain only alphanumerical characters. oppgaveId: $oppgaveId") + val oppgave = nasjonalOppgaveRepository.findByOppgaveId(oppgaveId.toInt()) ?: return null + + return oppgave + } + + fun findBySykmeldingId(sykmeldingId: String): NasjonalManuellOppgaveDAO? { + val oppgave = nasjonalOppgaveRepository.findBySykmeldingId(sykmeldingId) - if (existingId != null) { - nasjonalManuellOppgaveDAO.apply { - id = existingId + if (oppgave == null) return null + return oppgave + } + + fun getNasjonalOppgave(oppgaveId: String): NasjonalManuellOppgaveDAO { + val oppgave = findByOppgaveId(oppgaveId) + if (oppgave == null) { + log.warn("Fant ikke oppgave med id $oppgaveId Den kan kanskje være ferdigstilt fra før") + throw NoOppgaveException("Fant ikke oppgave") + } + log.info("Hentet oppgave med id $oppgaveId") + return oppgave + } + + suspend fun ferdigstillOppgave( + ferdigstillRegistrering: FerdigstillRegistrering, + beskrivelse: String?, + loggingMeta: LoggingMeta, + oppgaveId: String, + ) { + oppgaveClient.ferdigstillNasjonalOppgave(oppgaveId, ferdigstillRegistrering.sykmeldingId, ferdigstillRegistrering, loggingMeta) + } + + fun getOppgaveBySykmeldingId(sykmeldingId: String, authorization: String): NasjonalManuellOppgaveDAO? { + val sykmelding = findBySykmeldingId(sykmeldingId) + + if (sykmelding != null) { + log.info("papirsykmelding: henter sykmelding med id $sykmeldingId fra syk-dig-db") + securelog.info("hentet nasjonalOppgave fra db $sykmelding") + return sykmelding + } + log.info("papirsykmelding: henter ferdigstilt sykmelding med id $sykmeldingId gjennom syk-dig proxy") + val ferdigstiltSykmeldingRequest = smregistreringClient.getFerdigstiltSykmeldingRequest(authorization, sykmeldingId) + val papirManuellOppgave = ferdigstiltSykmeldingRequest.body + if (papirManuellOppgave != null) { + securelog.info("lagrer nasjonalOppgave i db $papirManuellOppgave") + val lagretOppgave = lagreOppgave(papirManuellOppgave, ferdigstilt = true) + return lagretOppgave + } + log.info( + "Fant ingen ferdigstilte sykmeldinger med sykmeldingId $sykmeldingId", + ) + return null + } + + fun getOppgave(oppgaveId: String, authorization: String): NasjonalManuellOppgaveDAO? { + val nasjonalOppgave = findByOppgaveId(oppgaveId) + if (nasjonalOppgave != null) { + log.info("papirsykmelding: henter oppgave med id $oppgaveId fra syk-dig-db") + return nasjonalOppgave + } + log.info("papirsykmelding: henter oppgave med id $oppgaveId gjennom syk-dig proxy") + val oppgave = smregistreringClient.getOppgaveRequest(authorization, oppgaveId) + log.info("har hentet papirManuellOppgave via syk-dig proxy") + + val papirManuellOppgave = oppgave.body + if (papirManuellOppgave != null) { + log.info("har hentet papirManuellOppgave via syk-dig proxy og oppgaven er ikke null") + securelog.info("lagrer nasjonalOppgave i db $papirManuellOppgave") + val lagretOppgave = lagreOppgave(papirManuellOppgave) + return lagretOppgave + } + log.info("Finner ikke uløst oppgave med id $oppgaveId") + return null + } + fun avvisOppgave( + oppgaveId: String, + request: String, + navEnhet: String, + authorization: String + ): ResponseEntity { + val eksisterendeOppgave = getOppgave(oppgaveId, authorization) + + if(eksisterendeOppgave == null) { + log.info("Fant ikke oppgave som skulle avvises: $oppgaveId") + return ResponseEntity(HttpStatus.NOT_FOUND) + } + + if(eksisterendeOppgave.ferdigstilt) { + log.info("Oppgave med id $oppgaveId er allerede ferdigstilt") + return ResponseEntity(HttpStatus.NO_CONTENT) } + + val avvisningsgrunn = mapper.readValue(request, AvvisSykmeldingRequest::class.java).reason + val veilederIdent = nasjonalCommonService.getNavIdent().veilederIdent + + ferdigstillNasjonalAvvistOppgave(eksisterendeOppgave, navEnhet, avvisningsgrunn, veilederIdent) + oppdaterOppgave( + eksisterendeOppgave.sykmeldingId, + utfall = Utfall.AVVIST.toString(), + ferdigstiltAv = veilederIdent, + avvisningsgrunn = avvisningsgrunn, + null + ) + + log.info("Har avvist oppgave med oppgaveId $oppgaveId") + return ResponseEntity(HttpStatus.NO_CONTENT) + } - return nasjonalManuellOppgaveDAO + +fun mapToDao( + papirManuellOppgave: PapirManuellOppgave, + existingId: UUID?, + ferdigstilt: Boolean = false +): NasjonalManuellOppgaveDAO { + mapper.registerModules(JavaTimeModule()) + securelog.info("Mapper til DAO: $papirManuellOppgave") + + val nasjonalManuellOppgaveDAO = + NasjonalManuellOppgaveDAO( + sykmeldingId = papirManuellOppgave.sykmeldingId, + journalpostId = papirManuellOppgave.papirSmRegistering.journalpostId, + fnr = papirManuellOppgave.fnr, + aktorId = papirManuellOppgave.papirSmRegistering.aktorId, + dokumentInfoId = papirManuellOppgave.papirSmRegistering.dokumentInfoId, + datoOpprettet = papirManuellOppgave.papirSmRegistering.datoOpprettet?.toLocalDateTime(), + oppgaveId = papirManuellOppgave.oppgaveid, + ferdigstilt = ferdigstilt, + papirSmRegistrering = + PapirSmRegistering( + journalpostId = papirManuellOppgave.papirSmRegistering.journalpostId, + oppgaveId = papirManuellOppgave.papirSmRegistering.oppgaveId, + fnr = papirManuellOppgave.papirSmRegistering.fnr, + aktorId = papirManuellOppgave.papirSmRegistering.aktorId, + dokumentInfoId = papirManuellOppgave.papirSmRegistering.dokumentInfoId, + datoOpprettet = papirManuellOppgave.papirSmRegistering.datoOpprettet, + sykmeldingId = papirManuellOppgave.papirSmRegistering.sykmeldingId, + syketilfelleStartDato = papirManuellOppgave.papirSmRegistering.syketilfelleStartDato, + arbeidsgiver = papirManuellOppgave.papirSmRegistering.arbeidsgiver, + medisinskVurdering = papirManuellOppgave.papirSmRegistering.medisinskVurdering, + skjermesForPasient = papirManuellOppgave.papirSmRegistering.skjermesForPasient, + perioder = papirManuellOppgave.papirSmRegistering.perioder, + prognose = papirManuellOppgave.papirSmRegistering.prognose, + utdypendeOpplysninger = papirManuellOppgave.papirSmRegistering.utdypendeOpplysninger, + tiltakNAV = papirManuellOppgave.papirSmRegistering.tiltakNAV, + tiltakArbeidsplassen = papirManuellOppgave.papirSmRegistering.tiltakArbeidsplassen, + andreTiltak = papirManuellOppgave.papirSmRegistering.andreTiltak, + meldingTilNAV = papirManuellOppgave.papirSmRegistering.meldingTilNAV, + meldingTilArbeidsgiver = papirManuellOppgave.papirSmRegistering.meldingTilArbeidsgiver, + kontaktMedPasient = papirManuellOppgave.papirSmRegistering.kontaktMedPasient, + behandletTidspunkt = papirManuellOppgave.papirSmRegistering.behandletTidspunkt, + behandler = papirManuellOppgave.papirSmRegistering.behandler, + ), + utfall = null, + ferdigstiltAv = null, + datoFerdigstilt = null, + avvisningsgrunn = null, + ) + + if (existingId != null) { + nasjonalManuellOppgaveDAO.apply { + id = existingId + } + } + + return nasjonalManuellOppgaveDAO +} + + fun mapFromDao( + nasjonalManuellOppgaveDAO: NasjonalManuellOppgaveDAO + ): PapirManuellOppgave { + val papirSmRegistering = nasjonalManuellOppgaveDAO.papirSmRegistrering + + requireNotNull(nasjonalManuellOppgaveDAO.oppgaveId) + requireNotNull(nasjonalManuellOppgaveDAO.dokumentInfoId) + return PapirManuellOppgave( + sykmeldingId = nasjonalManuellOppgaveDAO.sykmeldingId, + fnr = nasjonalManuellOppgaveDAO.fnr, + oppgaveid = nasjonalManuellOppgaveDAO.oppgaveId, + papirSmRegistering = PapirSmRegistering( + journalpostId = papirSmRegistering.journalpostId, + oppgaveId = papirSmRegistering.oppgaveId, + fnr = papirSmRegistering.fnr, + aktorId = papirSmRegistering.aktorId, + dokumentInfoId = papirSmRegistering.dokumentInfoId, + datoOpprettet = papirSmRegistering.datoOpprettet, + sykmeldingId = papirSmRegistering.sykmeldingId, + syketilfelleStartDato = papirSmRegistering.syketilfelleStartDato, + arbeidsgiver = papirSmRegistering.arbeidsgiver, + medisinskVurdering = papirSmRegistering.medisinskVurdering, + skjermesForPasient = papirSmRegistering.skjermesForPasient, + perioder = papirSmRegistering.perioder, + prognose = papirSmRegistering.prognose, + utdypendeOpplysninger = papirSmRegistering.utdypendeOpplysninger, + tiltakNAV = papirSmRegistering.tiltakNAV, + tiltakArbeidsplassen = papirSmRegistering.tiltakArbeidsplassen, + andreTiltak = papirSmRegistering.andreTiltak, + meldingTilNAV = papirSmRegistering.meldingTilNAV, + meldingTilArbeidsgiver = papirSmRegistering.meldingTilArbeidsgiver, + kontaktMedPasient = papirSmRegistering.kontaktMedPasient, + behandletTidspunkt = papirSmRegistering.behandletTidspunkt, + behandler = papirSmRegistering.behandler, + ), + pdfPapirSykmelding = byteArrayOf(), + documents = listOf(Document(dokumentInfoId = nasjonalManuellOppgaveDAO.dokumentInfoId, tittel = "papirsykmelding")), + ) } + + +fun ferdigstillNasjonalAvvistOppgave( + oppgave: NasjonalManuellOppgaveDAO, + navEnhet: String, + avvisningsgrunn: String?, + veilederIdent: String, +) { + + if (oppgave.fnr != null) { + val sykmeldt = + personService.getPerson( + id = oppgave.fnr, + callId = oppgave.sykmeldingId, + ) + val avvistGrunn = enumValues().find { it.name.equals(avvisningsgrunn, ignoreCase = true) } + ferdigstillingService.ferdigstillNasjonalAvvistJournalpost( + enhet = navEnhet, + oppgave = oppgave, + sykmeldt = sykmeldt, + avvisningsGrunn = avvistGrunn?.let { mapAvvisningsgrunn(it, null) }, + loggingMeta = getLoggingMeta(oppgave.sykmeldingId, oppgave), + ) + + } else { + log.error("Fant ikke fnr for oppgave med id $oppgave.oppgaveId") + } +} + +private fun getLoggingMeta(sykmeldingId: String, oppgave: NasjonalManuellOppgaveDAO): LoggingMeta { + return LoggingMeta( + mottakId = sykmeldingId, + dokumentInfoId = oppgave.dokumentInfoId, + msgId = sykmeldingId, + sykmeldingId = sykmeldingId, + journalpostId = oppgave.journalpostId, + ) +} +} + +fun isValidOppgaveId(oppgaveId: String): Boolean { + val regex = Regex("^\\d{9}$|^[a-zA-Z0-9]{1,20}$") + return oppgaveId.matches(regex) } diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalSykmeldingService.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalSykmeldingService.kt new file mode 100644 index 00000000..6a0928dd --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalSykmeldingService.kt @@ -0,0 +1,248 @@ +package no.nav.sykdig.digitalisering.papirsykmelding + +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import net.logstash.logback.argument.StructuredArguments +import no.nav.sykdig.LoggingMeta +import no.nav.sykdig.applog +import no.nav.sykdig.config.kafka.OK_SYKMELDING_TOPIC +import no.nav.sykdig.digitalisering.exceptions.SykmelderNotFoundException +import no.nav.sykdig.digitalisering.felles.Sykmelding +import no.nav.sykdig.digitalisering.helsenett.SykmelderService +import no.nav.sykdig.digitalisering.papirsykmelding.api.RegelClient +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.FerdigstillRegistrering +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.SmRegistreringManuell +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Sykmelder +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Veileder +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.checkValidState +import no.nav.sykdig.digitalisering.papirsykmelding.db.NasjonalSykmeldingRepository +import no.nav.sykdig.digitalisering.papirsykmelding.db.model.NasjonalManuellOppgaveDAO +import no.nav.sykdig.digitalisering.papirsykmelding.db.model.NasjonalSykmeldingDAO +import no.nav.sykdig.digitalisering.sykmelding.ReceivedSykmelding +import no.nav.sykdig.digitalisering.sykmelding.Status +import no.nav.sykdig.digitalisering.sykmelding.ValidationResult +import no.nav.sykdig.digitalisering.sykmelding.service.JournalpostService +import no.nav.sykdig.securelog +import no.nav.sykdig.utils.isWhitelisted +import org.apache.kafka.clients.producer.KafkaProducer +import org.apache.kafka.clients.producer.ProducerRecord +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity +import org.springframework.stereotype.Service +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneOffset + +@Service +class NasjonalSykmeldingService( + private val nasjonalOppgaveService: NasjonalOppgaveService, + private val nasjonalSykmeldingRepository: NasjonalSykmeldingRepository, + private val regelClient: RegelClient, + private val journalpostService: JournalpostService, + private val sykmeldingOKProducer: KafkaProducer, + private val sykmelderService: SykmelderService, + private val nasjonalCommonService: NasjonalCommonService, +) { + val log = applog() + val securelog = securelog() + + + private fun getLoggingMeta(sykmeldingId: String, oppgave: NasjonalManuellOppgaveDAO): LoggingMeta { + return LoggingMeta( + mottakId = sykmeldingId, + dokumentInfoId = oppgave.dokumentInfoId, + msgId = sykmeldingId, + sykmeldingId = sykmeldingId, + journalpostId = oppgave.journalpostId, + ) + } + + suspend fun sendPapirsykmelding(smRegistreringManuell: SmRegistreringManuell, navEnhet: String, callId: String, oppgaveId: String, authorization: String): ResponseEntity { + val oppgave = nasjonalOppgaveService.getOppgave(oppgaveId, authorization) ?: return ResponseEntity(HttpStatus.NOT_FOUND) + if (oppgave.ferdigstilt) { + log.info("Oppgave med id $oppgaveId er allerede ferdigstilt") + return ResponseEntity(HttpStatus.NO_CONTENT) + } + val sykmeldingId = oppgave.sykmeldingId + log.info("Forsøker å ferdigstille papirsykmelding med sykmeldingId $sykmeldingId") + + val loggingMeta = getLoggingMeta(sykmeldingId, oppgave) + val sykmelder = getSykmelder(smRegistreringManuell, loggingMeta, callId) + val receivedSykmelding = nasjonalCommonService.createReceivedSykmelding(sykmeldingId, oppgave, loggingMeta, smRegistreringManuell, callId, sykmelder) + + val validationResult = regelClient.valider(receivedSykmelding, sykmeldingId) + log.info( + "Resultat: {}, {}, {}", + StructuredArguments.keyValue("ruleStatus", validationResult.status.name), + StructuredArguments.keyValue( + "ruleHits", + validationResult.ruleHits.joinToString(", ", "(", ")") { it.ruleName }, + ), + StructuredArguments.fields(loggingMeta), + ) + checkValidState(smRegistreringManuell, sykmelder, validationResult) + + val dokumentInfoId = oppgave.dokumentInfoId + val journalpostId = oppgave.journalpostId + + val ferdigstillRegistrering = + FerdigstillRegistrering( + oppgaveId = oppgaveId.toInt(), + journalpostId = journalpostId, + dokumentInfoId = dokumentInfoId, + pasientFnr = receivedSykmelding.personNrPasient, + sykmeldingId = sykmeldingId, + sykmelder = sykmelder, + navEnhet = navEnhet, + veileder = nasjonalCommonService.getNavIdent(), + avvist = false, + oppgave = null, + ) + + if (!validationResult.ruleHits.isWhitelisted()) { + return handleBrokenRule(validationResult, oppgaveId.toInt()) + } + + return handleOK(validationResult, receivedSykmelding.copy(validationResult = validationResult), ferdigstillRegistrering, loggingMeta, null, smRegistreringManuell) + } + + private suspend fun handleOK( + validationResult: ValidationResult, + receivedSykmelding: ReceivedSykmelding, + ferdigstillRegistrering: FerdigstillRegistrering, + loggingMeta: LoggingMeta, + avvisningsgrunn: String?, + smRegistreringManuell: SmRegistreringManuell, + ): ResponseEntity { + if (validationResult.status == Status.OK || validationResult.status == Status.MANUAL_PROCESSING) { + val veileder = nasjonalCommonService.getNavIdent() + if (ferdigstillRegistrering.oppgaveId != null) { + journalpostService.ferdigstillNasjonalJournalpost( + ferdigstillRegistrering = ferdigstillRegistrering, + receivedSykmelding = receivedSykmelding, + loggingMeta = loggingMeta, + ) + nasjonalOppgaveService.ferdigstillOppgave( + ferdigstillRegistrering, + null, + loggingMeta, + ferdigstillRegistrering.oppgaveId.toString(), + ) + } + insertSykmeldingAndSendToKafka(receivedSykmelding, veileder) + nasjonalOppgaveService.oppdaterOppgave( + sykmeldingId = receivedSykmelding.sykmelding.id, + utfall = validationResult.status.toString(), + ferdigstiltAv = veileder.veilederIdent, + avvisningsgrunn = avvisningsgrunn, + smRegistreringManuell = smRegistreringManuell, + ) + log.info("Ferdigstilt papirsykmelding med sykmelding id ${receivedSykmelding.sykmelding.id}") + return ResponseEntity(HttpStatus.OK) + } + log.error( + "Ukjent status: ${validationResult.status} , papirsykmeldinger manuell registering kan kun ha ein av to typer statuser enten OK eller MANUAL_PROCESSING", + ) + return ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR) + } + + private fun insertSykmeldingAndSendToKafka( + receivedSykmelding: ReceivedSykmelding, + veileder: Veileder, + ) { + try { + sykmeldingOKProducer.send( + ProducerRecord(OK_SYKMELDING_TOPIC, receivedSykmelding.sykmelding.id, receivedSykmelding), + ).get() + log.info( + "Sykmelding sendt to kafka topic {} sykmelding id {}", + OK_SYKMELDING_TOPIC, + receivedSykmelding.sykmelding.id, + ) + } catch (exception: Exception) { + log.error("failed to send sykmelding to kafka result for sykmeldingId: {}", receivedSykmelding.sykmelding.id) + throw exception + } + securelog.info("receivedSykmelding som skal lagres: ${receivedSykmelding}") + val dao = mapToDao(receivedSykmelding, veileder) + nasjonalSykmeldingRepository.save(dao) + log.info("Sykmelding saved to db, nasjonal_sykmelding table {}", receivedSykmelding.sykmelding.id) + } + + private fun handleBrokenRule( + validationResult: ValidationResult, + oppgaveId: Int?, + ): ResponseEntity { + if (validationResult.status == Status.MANUAL_PROCESSING) { + log.info( + "Ferdigstilling av papirsykmeldinger manuell registering traff regel MANUAL_PROCESSING {}", + StructuredArguments.keyValue("oppgaveId", oppgaveId), + ) + return ResponseEntity.badRequest().body(validationResult) + } + if (validationResult.status == Status.OK) { + log.info( + "Ferdigstilling av papirsykmeldinger manuell registering traff regel MANUAL_PROCESSING {}", + StructuredArguments.keyValue("oppgaveId", oppgaveId), + ) + return ResponseEntity.badRequest().body(validationResult) + } + log.error("Ukjent status: ${validationResult.status} , papirsykmeldinger manuell registering kan kun ha ein av to typer statuser enten OK eller MANUAL_PROCESSING") + return ResponseEntity.internalServerError().body("En uforutsett feil oppsto ved validering av oppgaven") + } + + private suspend fun getSykmelder(smRegistreringManuell: SmRegistreringManuell, loggingMeta: LoggingMeta, callId: String): Sykmelder { + val sykmelderHpr = smRegistreringManuell.behandler.hpr + if (sykmelderHpr.isNullOrEmpty()) { + log.error("HPR-nummer mangler {}", StructuredArguments.fields(loggingMeta)) + throw SykmelderNotFoundException("HPR-nummer mangler") // dobbeltsjekk at det blir rett å throwe, returnerte bad request før + } + + log.info("Henter sykmelder fra HPR og PDL") + val sykmelder = sykmelderService.getSykmelder( + sykmelderHpr, + callId, + ) + return sykmelder + } + + + fun mapToDao( + receivedSykmelding: ReceivedSykmelding, + veileder: Veileder, + ): NasjonalSykmeldingDAO { + val mapper = jacksonObjectMapper() + mapper.registerModules(JavaTimeModule()) + val nasjonalManuellOppgaveDAO = + NasjonalSykmeldingDAO( + sykmeldingId = receivedSykmelding.sykmelding.id, + sykmelding = Sykmelding( + id = receivedSykmelding.sykmelding.id, + msgId = receivedSykmelding.sykmelding.msgId, + pasientAktoerId = receivedSykmelding.sykmelding.pasientAktoerId, + medisinskVurdering = receivedSykmelding.sykmelding.medisinskVurdering, + skjermesForPasient = receivedSykmelding.sykmelding.skjermesForPasient, + arbeidsgiver = receivedSykmelding.sykmelding.arbeidsgiver, + perioder = receivedSykmelding.sykmelding.perioder, + prognose = receivedSykmelding.sykmelding.prognose, + utdypendeOpplysninger = receivedSykmelding.sykmelding.utdypendeOpplysninger, + tiltakArbeidsplassen = receivedSykmelding.sykmelding.tiltakArbeidsplassen, + tiltakNAV = receivedSykmelding.sykmelding.tiltakNAV, + andreTiltak = receivedSykmelding.sykmelding.andreTiltak, + meldingTilNAV = receivedSykmelding.sykmelding.meldingTilNAV, + meldingTilArbeidsgiver = receivedSykmelding.sykmelding.meldingTilArbeidsgiver, + kontaktMedPasient = receivedSykmelding.sykmelding.kontaktMedPasient, + behandletTidspunkt = receivedSykmelding.sykmelding.behandletTidspunkt, + behandler = receivedSykmelding.sykmelding.behandler, + avsenderSystem = receivedSykmelding.sykmelding.avsenderSystem, + syketilfelleStartDato = receivedSykmelding.sykmelding.syketilfelleStartDato, + signaturDato = receivedSykmelding.sykmelding.signaturDato, + navnFastlege = receivedSykmelding.sykmelding.navnFastlege + ), + timestamp = OffsetDateTime.now(ZoneOffset.UTC), + ferdigstiltAv = veileder.veilederIdent, + datoFerdigstilt = LocalDateTime.now(ZoneOffset.UTC), + ) + return nasjonalManuellOppgaveDAO + } +} diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/NasjonalOppgaveController.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/NasjonalOppgaveController.kt index fd143e9c..b6200f9b 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/NasjonalOppgaveController.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/NasjonalOppgaveController.kt @@ -1,16 +1,22 @@ package no.nav.sykdig.digitalisering.papirsykmelding.api +import io.opentelemetry.instrumentation.annotations.WithSpan import no.nav.sykdig.applog import no.nav.sykdig.digitalisering.helsenett.SykmelderService +import no.nav.sykdig.digitalisering.papirsykmelding.NasjonalCommonService import no.nav.sykdig.digitalisering.papirsykmelding.NasjonalOppgaveService +import no.nav.sykdig.digitalisering.papirsykmelding.NasjonalSykmeldingService import no.nav.sykdig.digitalisering.papirsykmelding.api.model.PapirManuellOppgave import no.nav.sykdig.digitalisering.papirsykmelding.api.model.SmRegistreringManuell import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Sykmelder +import no.nav.sykdig.digitalisering.papirsykmelding.db.model.Utfall import no.nav.sykdig.digitalisering.pdl.Navn import no.nav.sykdig.digitalisering.pdl.PersonService import no.nav.sykdig.securelog +import org.springframework.http.HttpStatus import org.springframework.http.HttpStatusCode import org.springframework.http.ResponseEntity +import org.springframework.security.access.prepost.PostAuthorize import org.springframework.security.access.prepost.PreAuthorize import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable @@ -20,7 +26,7 @@ import org.springframework.web.bind.annotation.RequestHeader import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.ResponseBody import org.springframework.web.bind.annotation.RestController -import java.util.UUID +import java.util.* @RestController @RequestMapping("/api/v1/proxy") @@ -29,35 +35,44 @@ class NasjonalOppgaveController( private val nasjonalOppgaveService: NasjonalOppgaveService, private val sykmelderService: SykmelderService, private val personService: PersonService, + private val nasjonalSykmeldingService: NasjonalSykmeldingService, + private val nasjonalCommonService: NasjonalCommonService, ) { val log = applog() val securelog = securelog() @PostMapping("/oppgave/{oppgaveId}/avvis") + @PreAuthorize("@oppgaveSecurityService.hasAccessToNasjonalOppgave(#oppgaveId, #authorization)") + @WithSpan fun avvisOppgave( @PathVariable oppgaveId: String, - @RequestHeader("Authorization") authorization: String, @RequestHeader("X-Nav-Enhet") navEnhet: String, + @RequestHeader("Authorization") authorization: String, @RequestBody avvisSykmeldingRequest: String, ): ResponseEntity { - log.info("papirsykmelding: avviser oppgave med id $oppgaveId gjennom syk-dig proxy") - return smregistreringClient.postAvvisOppgaveRequest(authorization, oppgaveId, navEnhet, avvisSykmeldingRequest) + log.info("Forsøker å avvise oppgave med oppgaveId: $oppgaveId") + return nasjonalOppgaveService.avvisOppgave(oppgaveId, avvisSykmeldingRequest, navEnhet, authorization) } - @GetMapping("/oppgave/{oppgaveid}") + @GetMapping("/oppgave/{oppgaveId}") + @PostAuthorize("@oppgaveSecurityService.hasAccessToNasjonalOppgave(#oppgaveId, #authorization)") @ResponseBody + @WithSpan fun getPapirsykmeldingManuellOppgave( - @PathVariable oppgaveid: String, + @PathVariable oppgaveId: String, @RequestHeader("Authorization") authorization: String, - ): ResponseEntity { - log.info("papirsykmelding: henter oppgave med id $oppgaveid gjennom syk-dig proxy") - val oppgave = smregistreringClient.getOppgaveRequest(authorization, oppgaveid) - val papirManuellOppgave = oppgave.body + ): ResponseEntity { + val papirManuellOppgave = nasjonalOppgaveService.getOppgave(oppgaveId, authorization) + if (papirManuellOppgave != null) { - securelog.info("lagrer nasjonalOppgave i db $papirManuellOppgave") - // nasjonalOppgaveService.lagreOppgave(papirManuellOppgave) + if(papirManuellOppgave.ferdigstilt) { + log.info("Oppgave med id $oppgaveId er allerede ferdigstilt") + return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Oppgave med id $oppgaveId er allerede ferdigstilt") + } + return ResponseEntity.ok(nasjonalOppgaveService.mapFromDao(papirManuellOppgave)) } - return oppgave + + return ResponseEntity.notFound().build() } @GetMapping("/pasient") @@ -92,26 +107,40 @@ class NasjonalOppgaveController( } @PostMapping("/oppgave/{oppgaveId}/send") - fun sendOppgave( + @PreAuthorize("@oppgaveSecurityService.hasAccessToNasjonalOppgave(#oppgaveId, #authorization)") + @ResponseBody + @WithSpan + suspend fun sendOppgave( @PathVariable oppgaveId: String, @RequestHeader("Authorization") authorization: String, @RequestHeader("X-Nav-Enhet") navEnhet: String, @RequestBody papirSykmelding: SmRegistreringManuell, - ): ResponseEntity { - log.info("papirsykmelding: sender oppgave med oppgaveId $oppgaveId gjennom syk-dig proxy") - return smregistreringClient.postSendOppgaveRequest(authorization, oppgaveId, navEnhet, papirSykmelding) + ): ResponseEntity { + val callId = UUID.randomUUID().toString() + return nasjonalSykmeldingService.sendPapirsykmelding(papirSykmelding, navEnhet, callId, oppgaveId, authorization) + } @GetMapping("/sykmelding/{sykmeldingId}/ferdigstilt") + @PostAuthorize("@oppgaveSecurityService.hasAccessToNasjonalSykmelding(#sykmeldingId, #authorization)") @ResponseBody + @WithSpan fun getFerdigstiltSykmelding( @PathVariable sykmeldingId: String, @RequestHeader("Authorization") authorization: String, ): ResponseEntity { - log.info("papirsykmelding: henter ferdigstilt sykmelding med id $sykmeldingId gjennom syk-dig proxy") - return smregistreringClient.getFerdigstiltSykmeldingRequest(authorization, sykmeldingId) + val oppgave = nasjonalOppgaveService.getOppgaveBySykmeldingId(sykmeldingId, authorization) + if (oppgave != null) { + if(!oppgave.ferdigstilt) { + log.info("Oppgave med id $sykmeldingId er ikke ferdigstilt") + return ResponseEntity.status(HttpStatus.NOT_FOUND).build() + } + return ResponseEntity.ok(nasjonalOppgaveService.mapFromDao(oppgave)) + } + return ResponseEntity.notFound().build() } + @PostMapping("/oppgave/{oppgaveId}/tilgosys") fun sendOppgaveTilGosys( @PathVariable oppgaveId: String, @@ -122,6 +151,8 @@ class NasjonalOppgaveController( } @PostMapping("/sykmelding/{sykmeldingId}") + @PreAuthorize("@oppgaveSecurityService.hasAccessToNasjonalSykmelding(#sykmeldingId, #authorization)") + @WithSpan fun korrigerSykmelding( @PathVariable sykmeldingId: String, @RequestHeader("Authorization") authorization: String, @@ -129,7 +160,17 @@ class NasjonalOppgaveController( @RequestBody papirSykmelding: SmRegistreringManuell, ): ResponseEntity { log.info("papirsykmelding: Korrrigerer sykmelding med id $sykmeldingId gjennom syk-dig proxy") - return smregistreringClient.postKorrigerSykmeldingRequest(authorization, sykmeldingId, navEnhet, papirSykmelding) + val res = smregistreringClient.postKorrigerSykmeldingRequest(authorization, sykmeldingId, navEnhet, papirSykmelding) + + securelog.info("Oppdaterer korrigert oppgave i syk-dig-backend db $papirSykmelding") + nasjonalOppgaveService.oppdaterOppgave( + sykmeldingId = sykmeldingId, + utfall = Utfall.OK.toString(), + ferdigstiltAv = nasjonalCommonService.getNavIdent().veilederIdent, + avvisningsgrunn = null, + smRegistreringManuell = papirSykmelding, + ) + return res } @GetMapping("/pdf/{oppgaveId}/{dokumentInfoId}") diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/RegelClient.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/RegelClient.kt new file mode 100644 index 00000000..b59ffa76 --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/RegelClient.kt @@ -0,0 +1,40 @@ +package no.nav.sykdig.digitalisering.papirsykmelding.api + +import net.logstash.logback.argument.StructuredArguments.kv +import no.nav.sykdig.applog +import no.nav.sykdig.digitalisering.sykmelding.ReceivedSykmelding +import no.nav.sykdig.digitalisering.sykmelding.ValidationResult +import no.nav.sykdig.securelog +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.http.HttpStatus +import org.springframework.stereotype.Component +import org.springframework.web.client.HttpClientErrorException +import org.springframework.web.client.RestTemplate +import org.springframework.web.client.exchange + + +@Component +class RegelClient( + @Value("\${regel.url}") private val regelUrl: String, + private val regeltM2mRestTemplate: RestTemplate +){ + val log = applog() + val securelog = securelog() + + fun valider(sykmelding: ReceivedSykmelding, msgId: String): ValidationResult { + log.info("validating against rules {}", kv("sykmeldingId", sykmelding.sykmelding.id)) + val headers = HttpHeaders() + headers["Nav-CallId"] = msgId + + val response = regeltM2mRestTemplate.exchange( + "$regelUrl/api/v2/rules/validate", + HttpMethod.POST, + HttpEntity(sykmelding, headers), + ValidationResult::class.java + ) + return response.body ?: throw HttpClientErrorException(HttpStatus.NOT_FOUND, "regelvalidering feilet for sykmeldingId ${sykmelding.sykmelding.id}") + } +} diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/SmregistreringClient.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/SmregistreringClient.kt index 985754fe..cdfc29fe 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/SmregistreringClient.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/SmregistreringClient.kt @@ -5,6 +5,7 @@ import no.nav.sykdig.digitalisering.papirsykmelding.api.model.AvvisSykmeldingReq import no.nav.sykdig.digitalisering.papirsykmelding.api.model.PapirManuellOppgave import no.nav.sykdig.digitalisering.papirsykmelding.api.model.SmRegistreringManuell import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Sykmelder +import no.nav.sykdig.digitalisering.papirsykmelding.isValidOppgaveId import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpEntity import org.springframework.http.HttpHeaders @@ -57,6 +58,8 @@ class SmregistreringClient( authorization: String, oppgaveId: String, ): ResponseEntity { + if(!isValidOppgaveId(oppgaveId)) + throw IllegalArgumentException("Invalid oppgaveId does not contain only alphanumerical characters. oppgaveId: $oppgaveId") val headers = HttpHeaders() headers.contentType = MediaType.APPLICATION_JSON headers.setBearerAuth(removeBearerPrefix(authorization)) @@ -191,7 +194,7 @@ class SmregistreringClient( HttpEntity(papirSykmelding, headers), String::class.java, ) - log.info("Korrigering av sykmelding $sykmeldingId fikk følgende responskode ${res.statusCode}") + log.info("Korrigering av sykmelding $sykmeldingId fikk følgende responskode ${res.statusCode} der body er ${res.body}") return res } diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/FerdigstillRegistrering.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/FerdigstillRegistrering.kt new file mode 100644 index 00000000..8c0b5ed4 --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/FerdigstillRegistrering.kt @@ -0,0 +1,22 @@ +package no.nav.sykdig.digitalisering.papirsykmelding.api.model + +import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.NasjonalOppgaveResponse + + +data class FerdigstillRegistrering( + val oppgaveId: Int?, + val journalpostId: String, + val dokumentInfoId: String?, + val pasientFnr: String, + val sykmeldingId: String, + val sykmelder: Sykmelder, + val navEnhet: String, + val veileder: Veileder, + val avvist: Boolean, + val oppgave: NasjonalOppgaveResponse?, +) + +class Veileder( + val veilederIdent: String, +) + diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/PapirSykmelding.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/PapirSykmelding.kt index ed214f92..234dff76 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/PapirSykmelding.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/PapirSykmelding.kt @@ -1,12 +1,20 @@ package no.nav.sykdig.digitalisering.papirsykmelding.api.model +import no.nav.sykdig.digitalisering.felles.Arbeidsgiver +import no.nav.sykdig.digitalisering.felles.Behandler +import no.nav.sykdig.digitalisering.felles.KontaktMedPasient +import no.nav.sykdig.digitalisering.felles.MedisinskVurdering +import no.nav.sykdig.digitalisering.felles.MeldingTilNAV +import no.nav.sykdig.digitalisering.felles.Periode +import no.nav.sykdig.digitalisering.felles.Prognose +import no.nav.sykdig.digitalisering.felles.SporsmalSvar import java.time.LocalDate import java.time.OffsetDateTime data class PapirManuellOppgave( val fnr: String?, val sykmeldingId: String, - val oppgaveid: Int, + val oppgaveid: Int?, var pdfPapirSykmelding: ByteArray, val papirSmRegistering: PapirSmRegistering, val documents: List, @@ -47,9 +55,9 @@ data class AvvisSykmeldingRequest( ) data class Sykmelder( - val hprNummer: String?, - val fnr: String?, - val aktorId: String?, + val hprNummer: String, + val fnr: String, + val aktorId: String, val fornavn: String?, val mellomnavn: String?, val etternavn: String?, @@ -82,4 +90,4 @@ data class SmRegistreringManuell( val meldingTilArbeidsgiver: String?, val navnFastlege: String?, val harUtdypendeOpplysninger: Boolean, -) +) \ No newline at end of file diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/Sykmelding.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/Sykmelding.kt deleted file mode 100644 index 7c2e11aa..00000000 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/Sykmelding.kt +++ /dev/null @@ -1,200 +0,0 @@ -package no.nav.sykdig.digitalisering.papirsykmelding.api.model - -import java.time.LocalDate -import java.time.LocalDateTime - -data class Sykmelding( - val id: String, - val msgId: String, - val pasientAktoerId: String, - val medisinskVurdering: MedisinskVurdering, - val skjermesForPasient: Boolean, - val arbeidsgiver: Arbeidsgiver, - val perioder: List, - val prognose: Prognose?, - val utdypendeOpplysninger: Map>, - val tiltakArbeidsplassen: String?, - val tiltakNAV: String?, - val andreTiltak: String?, - val meldingTilNAV: MeldingTilNAV?, - val meldingTilArbeidsgiver: String?, - val kontaktMedPasient: KontaktMedPasient, - val behandletTidspunkt: LocalDateTime, - val behandler: Behandler, - val avsenderSystem: AvsenderSystem, - val syketilfelleStartDato: LocalDate?, - val signaturDato: LocalDateTime, - val navnFastlege: String?, -) - -data class MedisinskVurdering( - val hovedDiagnose: Diagnose?, - val biDiagnoser: List, - val svangerskap: Boolean, - val yrkesskade: Boolean, - val yrkesskadeDato: LocalDate?, - val annenFraversArsak: AnnenFraversArsak?, -) - -data class Diagnose(val system: String, val kode: String, val tekst: String?) - -data class AnnenFraversArsak(val beskrivelse: String?, val grunn: List) - -data class Arbeidsgiver( - val harArbeidsgiver: HarArbeidsgiver, - val navn: String?, - val yrkesbetegnelse: String?, - val stillingsprosent: Int?, -) - -enum class HarArbeidsgiver( - val codeValue: String, - val text: String, - val oid: String = "2.16.578.1.12.4.1.1.8130", -) { - EN_ARBEIDSGIVER("1", "Én arbeidsgiver"), - FLERE_ARBEIDSGIVERE("2", "Flere arbeidsgivere"), - INGEN_ARBEIDSGIVER("3", "Ingen arbeidsgiver"), -} - -data class Periode( - val fom: LocalDate, - val tom: LocalDate, - val aktivitetIkkeMulig: AktivitetIkkeMulig?, - val avventendeInnspillTilArbeidsgiver: String?, - val behandlingsdager: Int?, - val gradert: Gradert?, - val reisetilskudd: Boolean, -) - -data class AktivitetIkkeMulig( - val medisinskArsak: MedisinskArsak?, - val arbeidsrelatertArsak: ArbeidsrelatertArsak?, -) - -data class ArbeidsrelatertArsak( - val beskrivelse: String?, - val arsak: List, -) - -data class MedisinskArsak(val beskrivelse: String?, val arsak: List) - -enum class ArbeidsrelatertArsakType( - val codeValue: String, - val text: String, - val oid: String = "2.16.578.1.12.4.1.1.8132", -) { - MANGLENDE_TILRETTELEGGING("1", "Manglende tilrettelegging på arbeidsplassen"), - ANNET("9", "Annet"), -} - -enum class MedisinskArsakType( - val codeValue: String, - val text: String, - val oid: String = "2.16.578.1.12.4.1.1.8133", -) { - TILSTAND_HINDRER_AKTIVITET("1", "Helsetilstanden hindrer pasienten i å være i aktivitet"), - AKTIVITET_FORVERRER_TILSTAND("2", "Aktivitet vil forverre helsetilstanden"), - AKTIVITET_FORHINDRER_BEDRING("3", "Aktivitet vil hindre/forsinke bedring av helsetilstanden"), - ANNET("9", "Annet"), -} - -data class Gradert(val reisetilskudd: Boolean, val grad: Int) - -data class Prognose( - val arbeidsforEtterPeriode: Boolean, - val hensynArbeidsplassen: String?, - val erIArbeid: ErIArbeid?, - val erIkkeIArbeid: ErIkkeIArbeid?, -) - -data class ErIArbeid( - val egetArbeidPaSikt: Boolean, - val annetArbeidPaSikt: Boolean, - val arbeidFOM: LocalDate?, - val vurderingsdato: LocalDate?, -) - -data class ErIkkeIArbeid( - val arbeidsforPaSikt: Boolean, - val arbeidsforFOM: LocalDate?, - val vurderingsdato: LocalDate?, -) - -data class MeldingTilNAV(val bistandUmiddelbart: Boolean, val beskrivBistand: String?) - -data class KontaktMedPasient(val kontaktDato: LocalDate?, val begrunnelseIkkeKontakt: String?) - -data class Behandler( - val fornavn: String, - val mellomnavn: String?, - val etternavn: String, - val aktoerId: String, - val fnr: String, - val hpr: String?, - val her: String?, - val adresse: Adresse, - val tlf: String?, -) - -data class Adresse( - val gate: String?, - val postnummer: Int?, - val kommune: String?, - val postboks: String?, - val land: String?, -) - -data class AvsenderSystem(val navn: String, val versjon: String) - -data class SporsmalSvar( - val sporsmal: String, - val svar: String, - val restriksjoner: List, -) - -enum class SvarRestriksjon( - val codeValue: String, - val text: String, - val oid: String = "2.16.578.1.12.4.1.1.8134", -) { - SKJERMET_FOR_ARBEIDSGIVER("A", "Informasjonen skal ikke vises arbeidsgiver"), - SKJERMET_FOR_PASIENT("P", "Informasjonen skal ikke vises pasient"), - SKJERMET_FOR_NAV("N", "Informasjonen skal ikke vises NAV"), -} - -enum class AnnenFraverGrunn( - val codeValue: String, - val text: String, - val oid: String = "2.16.578.1.12.4.1.1.8131", -) { - GODKJENT_HELSEINSTITUSJON("1", "Når vedkommende er innlagt i en godkjent helseinstitusjon"), - BEHANDLING_FORHINDRER_ARBEID( - "2", - "Når vedkommende er under behandling og legen erklærer at behandlingen gjør det nødvendig at vedkommende ikke arbeider", - ), - ARBEIDSRETTET_TILTAK("3", "Når vedkommende deltar på et arbeidsrettet tiltak"), - MOTTAR_TILSKUDD_GRUNNET_HELSETILSTAND( - "4", - "Når vedkommende på grunn av sykdom, skade eller lyte får tilskott når vedkommende på grunn av sykdom, " + - "skade eller lyte får tilskott", - ), - NODVENDIG_KONTROLLUNDENRSOKELSE( - "5", - "Når vedkommende er til nødvendig kontrollundersøkelse som krever minst 24 timers fravær, reisetid medregnet", - ), - SMITTEFARE( - "6", - "Når vedkommende myndighet har nedlagt forbud mot at han eller hun arbeider på grunn av smittefare", - ), - ABORT("7", "Når vedkommende er arbeidsufør som følge av svangerskapsavbrudd"), - UFOR_GRUNNET_BARNLOSHET( - "8", - "Når vedkommende er arbeidsufør som følge av behandling for barnløshet", - ), - DONOR("9", "Når vedkommende er donor eller er under vurdering som donor"), - BEHANDLING_STERILISERING( - "10", - "Når vedkommende er arbeidsufør som følge av behandling i forbindelse med sterilisering", - ), -} diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/ValidationRules.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/ValidationRules.kt new file mode 100644 index 00000000..eca80093 --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/ValidationRules.kt @@ -0,0 +1,183 @@ +package no.nav.sykdig.digitalisering.papirsykmelding.api.model + +import java.time.LocalDate +import no.nav.sykdig.digitalisering.exceptions.ValidationException +import no.nav.sykdig.digitalisering.felles.Periode +import no.nav.sykdig.digitalisering.sykmelding.RuleInfo +import no.nav.sykdig.digitalisering.sykmelding.Status +import no.nav.sykdig.digitalisering.sykmelding.ValidationResult + +const val HPR_GODKJENNING_KODE = 7704 + +fun checkValidState( + smRegistreringManuell: SmRegistreringManuell, + sykmelder: Sykmelder, + validationResult: ValidationResult, +) { + when { + smRegistreringManuell.perioder.isEmpty() -> { + val vr = + ValidationResult( + status = Status.MANUAL_PROCESSING, + ruleHits = + listOf( + RuleInfo( + ruleName = "periodeValidation", + messageForSender = + "Sykmeldingen må ha minst én periode oppgitt for å være gyldig", + messageForUser = + "Sykmelder har gjort en feil i utfyllingen av sykmeldingen.", + ruleStatus = Status.MANUAL_PROCESSING, + ), + ), + ) + throw ValidationException(vr) + } + harOverlappendePerioder(smRegistreringManuell.perioder) -> { + val vr = + ValidationResult( + status = Status.MANUAL_PROCESSING, + ruleHits = + listOf( + RuleInfo( + ruleName = "overlappendePeriodeValidation", + messageForSender = "Sykmeldingen har overlappende perioder", + messageForUser = + "Sykmelder har gjort en feil i utfyllingen av sykmeldingen.", + ruleStatus = Status.MANUAL_PROCESSING, + ), + ), + ) + throw ValidationException(vr) + } + harUlovligKombinasjonMedReisetilskudd(smRegistreringManuell.perioder) -> { + val vr = + ValidationResult( + status = Status.MANUAL_PROCESSING, + ruleHits = + listOf( + RuleInfo( + ruleName = "reisetilskuddValidation", + messageForSender = + "Sykmeldingen inneholder periode som kombinerer reisetilskudd med annen sykmeldingstype", + messageForUser = + "Sykmelder har gjort en feil i utfyllingen av sykmeldingen.", + ruleStatus = Status.MANUAL_PROCESSING, + ), + ), + ) + throw ValidationException(vr) + } + erFremtidigDato(smRegistreringManuell.behandletDato) -> { + val vr = + ValidationResult( + status = Status.MANUAL_PROCESSING, + ruleHits = + listOf( + RuleInfo( + ruleName = "behandletDatoValidation", + messageForSender = "Behandletdato kan ikke være frem i tid.", + messageForUser = + "Sykmelder har gjort en feil i utfyllingen av sykmeldingen.", + ruleStatus = Status.MANUAL_PROCESSING, + ), + ), + ) + throw ValidationException(vr) + } + studentBehandlerUtenAutorisasjon(validationResult, sykmelder) -> { + val vr = + ValidationResult( + status = Status.MANUAL_PROCESSING, + ruleHits = + listOf( + RuleInfo( + ruleName = + RuleHitCustomError.BEHANDLER_MANGLER_AUTORISASJON_I_HPR.name, + messageForSender = + "Studenter har ikke lov til å skrive sykmelding. Sykmelding må avvises.", + messageForUser = "Studenter har ikke lov til å skrive sykmelding.", + ruleStatus = Status.MANUAL_PROCESSING, + ), + ), + ) + throw ValidationException(vr) + } + suspendertBehandler(validationResult) -> { + val vr = + ValidationResult( + status = Status.MANUAL_PROCESSING, + ruleHits = + listOf( + RuleInfo( + ruleName = RuleHitCustomError.BEHANDLER_SUSPENDERT.name, + messageForSender = + "Legen har mistet retten til å skrive sykmelding.", + messageForUser = "Legen har mistet retten til å skrive sykmelding.", + ruleStatus = Status.MANUAL_PROCESSING, + ), + ), + ) + throw ValidationException(vr) + } + } +} + +fun suspendertBehandler(validationResult: ValidationResult): Boolean { + return validationResult.ruleHits.any { + it.ruleName == RuleHitCustomError.BEHANDLER_SUSPENDERT.name + } +} + +fun studentBehandlerUtenAutorisasjon( + validationResult: ValidationResult, + sykmelder: Sykmelder +): Boolean { + val behandlerManglerAutorisasjon = + validationResult.ruleHits.any { + it.ruleName == RuleHitCustomError.BEHANDLER_MANGLER_AUTORISASJON_I_HPR.name + } + + val erStudent = + sykmelder.godkjenninger?.any { + it.autorisasjon?.aktiv == true && + it.autorisasjon.oid == HPR_GODKJENNING_KODE && + it.autorisasjon.verdi == "3" + } + + return behandlerManglerAutorisasjon && erStudent == true +} + +fun harOverlappendePerioder(perioder: List): Boolean { + return harIdentiskePerioder(perioder) || + perioder.any { periodA -> + perioder + .filter { periodB -> periodB != periodA } + .any { periodB -> periodA.fom in periodB.range() || periodA.tom in periodB.range() } + } +} + +private fun harIdentiskePerioder(perioder: List): Boolean { + return perioder.distinct().count() != perioder.size +} + +fun harUlovligKombinasjonMedReisetilskudd(perioder: List): Boolean { + perioder.forEach { + if ( + it.reisetilskudd && + (it.aktivitetIkkeMulig != null || + it.gradert != null || + it.avventendeInnspillTilArbeidsgiver != null || + it.behandlingsdager != null) + ) { + return true + } + } + return false +} + +fun Periode.range(): ClosedRange = fom.rangeTo(tom) + +fun erFremtidigDato(dato: LocalDate): Boolean { + return dato.isAfter(LocalDate.now()) +} diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/WhitelistedRuleHit.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/WhitelistedRuleHit.kt new file mode 100644 index 00000000..35ff19f1 --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/api/model/WhitelistedRuleHit.kt @@ -0,0 +1,18 @@ +package no.nav.sykdig.digitalisering.papirsykmelding.api.model + +enum class WhitelistedRuleHit { + INNTIL_8_DAGER, + INNTIL_30_DAGER, //fjerne etter prodsetting av regler. + INNTIL_1_MAANED, + INNTIL_1_MAANED_MED_BEGRUNNELSE, + OVER_1_MAANED, + INNTIL_30_DAGER_MED_BEGRUNNELSE, //fjerne + OVER_30_DAGER, //fjerne + FREMDATERT, + PASIENTEN_HAR_KODE_6, +} + +enum class RuleHitCustomError { + BEHANDLER_MANGLER_AUTORISASJON_I_HPR, + BEHANDLER_SUSPENDERT, +} diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/NasjonalOppgaveRepository.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/NasjonalOppgaveRepository.kt index 4391b3c0..b693f727 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/NasjonalOppgaveRepository.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/NasjonalOppgaveRepository.kt @@ -2,6 +2,7 @@ package no.nav.sykdig.digitalisering.papirsykmelding.db import no.nav.sykdig.digitalisering.papirsykmelding.db.model.NasjonalManuellOppgaveDAO import org.springframework.data.repository.CrudRepository +import org.springframework.data.repository.kotlin.CoroutineCrudRepository import org.springframework.stereotype.Repository import org.springframework.transaction.annotation.Transactional import java.util.Optional @@ -10,5 +11,7 @@ import java.util.UUID @Transactional @Repository interface NasjonalOppgaveRepository : CrudRepository { - fun findBySykmeldingId(sykmeldingId: String): Optional + fun findBySykmeldingId(sykmeldingId: String): NasjonalManuellOppgaveDAO? + fun findByOppgaveId(oppgaveId: Int): NasjonalManuellOppgaveDAO? + fun findByOppgaveIdAndFerdigstiltIsFalse(oppgaveId: Int): NasjonalManuellOppgaveDAO? } diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/NasjonalSykmeldingRepository.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/NasjonalSykmeldingRepository.kt index 37ce6aec..383951c2 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/NasjonalSykmeldingRepository.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/NasjonalSykmeldingRepository.kt @@ -4,10 +4,10 @@ import no.nav.sykdig.digitalisering.papirsykmelding.db.model.NasjonalSykmeldingD import org.springframework.data.repository.CrudRepository import org.springframework.stereotype.Repository import org.springframework.transaction.annotation.Transactional -import java.util.UUID +import java.util.* @Transactional @Repository interface NasjonalSykmeldingRepository : CrudRepository { - fun findBySykmeldingId(sykmeldingId: String): NasjonalSykmeldingDAO? + fun findBySykmeldingId(sykmeldingId: String): Optional } diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/CustomPgConverters.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/CustomPgConverters.kt new file mode 100644 index 00000000..cec7e0ce --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/CustomPgConverters.kt @@ -0,0 +1,87 @@ +package no.nav.sykdig.digitalisering.papirsykmelding.db.model + +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import no.nav.sykdig.digitalisering.felles.Sykmelding +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.PapirSmRegistering +import no.nav.sykdig.objectMapper +import org.postgresql.util.PGobject +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.core.convert.converter.Converter +import org.springframework.data.convert.ReadingConverter +import org.springframework.data.convert.WritingConverter +import org.springframework.data.jdbc.core.convert.JdbcCustomConversions +import java.time.OffsetDateTime +import java.time.ZoneOffset + + +@ReadingConverter +class OffsetDateTimeReadingConverter : Converter { + override fun convert(source: Any): OffsetDateTime { + return when (source) { + is java.sql.Timestamp -> source.toInstant().atOffset(ZoneOffset.UTC) + is OffsetDateTime -> source // If already OffsetDateTime, return as-is + else -> throw IllegalArgumentException("Unexpected source type: ${source::class}") + } + } +} + +@WritingConverter +class PapirSmRegistreringWritingConverter : Converter { + override fun convert(source: PapirSmRegistering): PGobject { + val jsonObject = PGobject() + jsonObject.type = "jsonb" + objectMapper.registerModule(JavaTimeModule()) + jsonObject.value = objectMapper.writeValueAsString(source) + return jsonObject + } +} + +@ReadingConverter +class PapirSmRegistreringReadingConverter : Converter { + private val objectMapper = jacksonObjectMapper() + + override fun convert(source: PGobject): PapirSmRegistering { + objectMapper.registerModule(JavaTimeModule()) + return objectMapper.readValue(source.value!!, PapirSmRegistering::class.java) // bedre håndtering enn !! + } +} + +@WritingConverter +class SykmeldingWritingConverter : Converter { + override fun convert(source: Sykmelding): PGobject { + val jsonObject = PGobject() + jsonObject.type = "jsonb" + objectMapper.registerModule(JavaTimeModule()) + jsonObject.value = objectMapper.writeValueAsString(source) + return jsonObject + } +} + +@ReadingConverter +class SykmeldingReadingConverter : Converter { + private val objectMapper = jacksonObjectMapper() + + override fun convert(source: PGobject): Sykmelding { + objectMapper.registerModule(JavaTimeModule()) + return objectMapper.readValue(source.value!!, Sykmelding::class.java) // bedre håndtering enn !! + } +} + +@Configuration +class JdbcConfiguration { + @Bean + fun jdbcCustomConversions(): JdbcCustomConversions { + return JdbcCustomConversions( + listOf( + OffsetDateTimeReadingConverter(), + PapirSmRegistreringWritingConverter(), + PapirSmRegistreringReadingConverter(), + SykmeldingWritingConverter(), + SykmeldingReadingConverter(), + ), + ) + } +} + diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/NasjonalManuellOppgaveDAO.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/NasjonalManuellOppgaveDAO.kt index 5a3e4aa7..da0a0dd0 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/NasjonalManuellOppgaveDAO.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/NasjonalManuellOppgaveDAO.kt @@ -19,7 +19,7 @@ import java.time.LocalDateTime import java.util.UUID @Table(name = "nasjonal_manuelloppgave") -open class NasjonalManuellOppgaveDAO( +data class NasjonalManuellOppgaveDAO( @Id @GeneratedValue(generator = "UUID") var id: UUID? = null, @@ -51,36 +51,4 @@ open class NasjonalManuellOppgaveDAO( var avvisningsgrunn: String? = null, ) -@WritingConverter -class PapirSmRegistreringWritingConverter : Converter { - override fun convert(source: PapirSmRegistering): PGobject { - val jsonObject = PGobject() - jsonObject.type = "jsonb" - objectMapper.registerModule(JavaTimeModule()) - jsonObject.value = objectMapper.writeValueAsString(source) - return jsonObject - } -} -@ReadingConverter -class PapirSmRegistreringReadingConverter : Converter { - private val objectMapper = jacksonObjectMapper() - - override fun convert(source: PGobject): PapirSmRegistering { - objectMapper.registerModule(JavaTimeModule()) - return objectMapper.readValue(source.value!!, PapirSmRegistering::class.java) // bedre håndtering enn !! - } -} - -@Configuration -class JdbcConfiguration { - @Bean - fun jdbcCustomConversions(): JdbcCustomConversions { - return JdbcCustomConversions( - listOf( - PapirSmRegistreringWritingConverter(), - PapirSmRegistreringReadingConverter(), - ), - ) - } -} diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/NasjonalSykmeldingDAO.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/NasjonalSykmeldingDAO.kt index 0275096a..5b1ce62d 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/NasjonalSykmeldingDAO.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/NasjonalSykmeldingDAO.kt @@ -1,13 +1,13 @@ package no.nav.sykdig.digitalisering.papirsykmelding.db.model import jakarta.persistence.GeneratedValue -import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Sykmelding +import no.nav.sykdig.digitalisering.felles.Sykmelding import org.springframework.data.annotation.Id import org.springframework.data.relational.core.mapping.Column import org.springframework.data.relational.core.mapping.Table import java.time.LocalDateTime import java.time.OffsetDateTime -import java.util.UUID +import java.util.* @Table(name = "nasjonal_sykmelding") open class NasjonalSykmeldingDAO( diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/Utfall.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/Utfall.kt new file mode 100644 index 00000000..b4c0ceb9 --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/db/model/Utfall.kt @@ -0,0 +1,7 @@ +package no.nav.sykdig.digitalisering.papirsykmelding.db.model + +enum class Utfall { + OK, + SENDT_TIL_GOSYS, + AVVIST, +} \ No newline at end of file diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/saf/SafJournalpostService.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/saf/SafJournalpostService.kt index 379bc593..4443a2f0 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/saf/SafJournalpostService.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/saf/SafJournalpostService.kt @@ -2,9 +2,11 @@ package no.nav.sykdig.digitalisering.saf import no.nav.syfo.oppgave.saf.model.DokumentMedTittel import no.nav.sykdig.applog +import no.nav.sykdig.digitalisering.exceptions.MissingJournalpostException import no.nav.sykdig.digitalisering.saf.graphql.DokumentInfo import no.nav.sykdig.digitalisering.saf.graphql.Journalstatus import no.nav.sykdig.digitalisering.saf.graphql.SafJournalpost +import no.nav.sykdig.generated.types.Journalpost import org.springframework.stereotype.Component @Component @@ -55,6 +57,12 @@ class SafJournalpostService( ?: false } + fun erIkkeJournalfort(journalpostId: String): Boolean { + val journalpost = safJournalpostGraphQlClient.getJournalpostM2m(journalpostId) + if (journalpost.journalpost == null) throw MissingJournalpostException("Journalpost med id $journalpostId finnes ikke i SAF") + return erIkkeJournalfort(journalpost.journalpost) + } + private fun finnDokumentInfoIdForSykmeldingPdfListe( dokumentListe: List?, sykmeldingId: String, diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/sykmelding/ReceivedSykmelding.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/sykmelding/ReceivedSykmelding.kt index 85c47807..7e668a3f 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/sykmelding/ReceivedSykmelding.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/sykmelding/ReceivedSykmelding.kt @@ -1,5 +1,6 @@ package no.nav.sykdig.digitalisering.sykmelding +import no.nav.sykdig.digitalisering.felles.Sykmelding import java.time.LocalDateTime data class ReceivedSykmelding( @@ -29,5 +30,5 @@ data class ReceivedSykmelding( * TSS-ident, this is only used for infotrygd compat and should be removed in thefuture */ val tssid: String?, - val validationResult: ValidationResult, + val validationResult: ValidationResult? = null, ) diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/sykmelding/service/JournalpostService.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/sykmelding/service/JournalpostService.kt index 798e867d..869c76d2 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/sykmelding/service/JournalpostService.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/sykmelding/service/JournalpostService.kt @@ -1,12 +1,19 @@ package no.nav.sykdig.digitalisering.sykmelding.service import net.logstash.logback.argument.StructuredArguments.kv +import no.nav.sykdig.LoggingMeta +import no.nav.sykdig.applog import no.nav.sykdig.digitalisering.SykDigOppgaveService +import no.nav.sykdig.digitalisering.dokarkiv.DokarkivClient +import no.nav.sykdig.digitalisering.papirsykmelding.NasjonalCommonService +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.FerdigstillRegistrering import no.nav.sykdig.digitalisering.pdl.PersonService +import no.nav.sykdig.digitalisering.saf.SafJournalpostService import no.nav.sykdig.digitalisering.saf.graphql.SafJournalpost import no.nav.sykdig.digitalisering.saf.graphql.TEMA_SYKEPENGER import no.nav.sykdig.digitalisering.saf.graphql.TEMA_SYKMELDING import no.nav.sykdig.digitalisering.saf.graphql.Type +import no.nav.sykdig.digitalisering.sykmelding.ReceivedSykmelding import no.nav.sykdig.digitalisering.sykmelding.db.JournalpostSykmeldingRepository import no.nav.sykdig.generated.types.Document import no.nav.sykdig.generated.types.Journalpost @@ -24,9 +31,13 @@ class JournalpostService( private val sykDigOppgaveService: SykDigOppgaveService, private val journalpostSykmeldingRepository: JournalpostSykmeldingRepository, private val metricRegister: MetricRegister, + private val safJournalpostService: SafJournalpostService, + private val dokarkivClient: DokarkivClient, + private val nasjonalCommonService: NasjonalCommonService, ) { companion object { private val securelog = securelog() + private val log = applog() } fun createSykmeldingFromJournalpost( @@ -65,7 +76,7 @@ class JournalpostService( ) val fnr = personService.getPerson(fnrEllerAktorId, journalpostId).fnr val aktorId = personService.getPerson(fnrEllerAktorId, journalpostId).aktorId - val oppgaveId = sykDigOppgaveService.opprettOgLagreOppgave(journalpost, journalpostId, fnr, aktorId) + val oppgaveId = sykDigOppgaveService.opprettOgLagreOppgave(journalpost, journalpostId, fnr, aktorId, nasjonalCommonService.getNavEmail()) securelog.info( "oppretter sykmelding fra journalpost {} {} {} {}", @@ -120,6 +131,33 @@ class JournalpostService( fnr = fnr, ) } + suspend fun ferdigstillNasjonalJournalpost( + ferdigstillRegistrering: FerdigstillRegistrering, + receivedSykmelding: ReceivedSykmelding, + loggingMeta: LoggingMeta, + ) { + if ( + safJournalpostService.erIkkeJournalfort(journalpostId = ferdigstillRegistrering.journalpostId) + ) { + dokarkivClient.oppdaterOgFerdigstillNasjonalJournalpost( + journalpostId = ferdigstillRegistrering.journalpostId, + dokumentInfoId = ferdigstillRegistrering.dokumentInfoId, + pasientFnr = ferdigstillRegistrering.pasientFnr, + sykmeldingId = ferdigstillRegistrering.sykmeldingId, + sykmelder = ferdigstillRegistrering.sykmelder, + loggingMeta = loggingMeta, + navEnhet = ferdigstillRegistrering.navEnhet, + avvist = ferdigstillRegistrering.avvist, + receivedSykmelding = receivedSykmelding + ) + } else { + log.info( + "Hopper over oppdaterOgFerdigstillJournalpost, " + + "journalpostId ${ferdigstillRegistrering.journalpostId} er allerede journalført", + ) + } + } + fun isSykmeldingCreated(id: String): Boolean { return journalpostSykmeldingRepository.getJournalpostSykmelding(id) != null diff --git a/src/main/kotlin/no/nav/sykdig/digitalisering/tilgangskontroll/OppgaveSecurityService.kt b/src/main/kotlin/no/nav/sykdig/digitalisering/tilgangskontroll/OppgaveSecurityService.kt index 597b1750..ff9488b5 100644 --- a/src/main/kotlin/no/nav/sykdig/digitalisering/tilgangskontroll/OppgaveSecurityService.kt +++ b/src/main/kotlin/no/nav/sykdig/digitalisering/tilgangskontroll/OppgaveSecurityService.kt @@ -3,6 +3,8 @@ package no.nav.sykdig.digitalisering.tilgangskontroll import no.nav.sykdig.auditLogger.AuditLogger import no.nav.sykdig.auditlog import no.nav.sykdig.digitalisering.SykDigOppgaveService +import no.nav.sykdig.digitalisering.papirsykmelding.NasjonalCommonService +import no.nav.sykdig.digitalisering.papirsykmelding.NasjonalOppgaveService import no.nav.sykdig.digitalisering.pdl.PersonService import no.nav.sykdig.digitalisering.saf.SafJournalpostGraphQlClient import no.nav.sykdig.digitalisering.saf.graphql.Type @@ -11,8 +13,6 @@ import no.nav.sykdig.generated.types.JournalpostResult import no.nav.sykdig.generated.types.JournalpostStatus import no.nav.sykdig.objectMapper import no.nav.sykdig.securelog -import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken import org.springframework.stereotype.Service @Service @@ -21,6 +21,8 @@ class OppgaveSecurityService( private val sykDigOppgaveService: SykDigOppgaveService, private val safGraphQlClient: SafJournalpostGraphQlClient, private val personService: PersonService, + private val nasjonalOppgaveService: NasjonalOppgaveService, + private val nasjonalCommonService: NasjonalCommonService, ) { companion object { private val securelog = securelog() @@ -30,77 +32,101 @@ class OppgaveSecurityService( fun hasAccessToOppgave(oppgaveId: String): Boolean { securelog.info("sjekker om bruker har tilgang på oppgave $oppgaveId") val oppgave = sykDigOppgaveService.getOppgave(oppgaveId) - val navEmail = getNavEmail() + val navEmail = nasjonalCommonService.getNavEmail() val tilgang = hasAccess(oppgave.fnr, navEmail) securelog.info("Innlogget bruker: $navEmail har${if (!tilgang) " ikke" else ""} tilgang til oppgave med id $oppgaveId") return tilgang } - fun hasAccessToSykmelding(sykmeldingId: String): Boolean { - securelog.info("sjekker om bruker har tilgang på sykmelding $sykmeldingId") - val oppgave = sykDigOppgaveService.getOppgaveFromSykmeldingId(sykmeldingId) - val navEmail = getNavEmail() - val tilgang = hasAccess(oppgave.fnr, navEmail) - securelog.info("Innlogget bruker: $navEmail har${if (!tilgang) " ikke" else ""} tilgang til oppgave med id $sykmeldingId") - return tilgang + fun hasAccessToNasjonalOppgave(oppgaveId: String, authorization: String): Boolean { + securelog.info("sjekker om bruker har tilgang på oppgave $oppgaveId") + val oppgave = nasjonalOppgaveService.getOppgave(oppgaveId, authorization) + val navEmail = nasjonalCommonService.getNavEmail() + val fnr = oppgave?.fnr + if (oppgave != null && fnr != null) { + val tilgang = hasAccess(fnr, navEmail) + securelog.info("Innlogget bruker: $navEmail har${if (!tilgang) " ikke" else ""} tilgang til oppgave med id $oppgaveId") + return tilgang + } + return false + } + + fun hasAccessToNasjonalSykmelding(sykmeldingId: String, authorization: String): Boolean { + securelog.info("sjekker om bruker har tilgang på sykmelding $sykmeldingId") + val oppgave = nasjonalOppgaveService.findBySykmeldingId(sykmeldingId) + val navEmail = nasjonalCommonService.getNavEmail() + val fnr = oppgave?.fnr + if (oppgave != null && fnr != null) { + val tilgang = hasAccess(fnr, navEmail) + securelog.info("Innlogget bruker: $navEmail har${if (!tilgang) " ikke" else ""} tilgang til oppgave med id $sykmeldingId") + return tilgang + } + return false } + fun hasAccessToSykmelding(sykmeldingId: String): Boolean { + securelog.info("sjekker om bruker har tilgang på sykmelding $sykmeldingId") + val oppgave = sykDigOppgaveService.getOppgaveFromSykmeldingId(sykmeldingId) + val navEmail = nasjonalCommonService.getNavEmail() + val tilgang = hasAccess(oppgave.fnr, navEmail) + securelog.info("Innlogget bruker: $navEmail har${if (!tilgang) " ikke" else ""} tilgang til oppgave med id $sykmeldingId") + return tilgang + } - fun hasAccessToJournalpost(journalpostResult: JournalpostResult): Boolean { - return when (journalpostResult) { - is Journalpost -> return hasAccess(journalpostResult.fnr, getNavEmail()) - is JournalpostStatus -> return true - else -> false + fun hasAccessToJournalpost(journalpostResult: JournalpostResult): Boolean { + return when (journalpostResult) { + is Journalpost -> return hasAccess(journalpostResult.fnr, nasjonalCommonService.getNavEmail()) + is JournalpostStatus -> return true + else -> false + } } - } - fun hasAccessToJournalpostId(journalpostId: String): Boolean { - val journalpost = safGraphQlClient.getJournalpost(journalpostId) - securelog.info("journalpostid $journalpostId ble hentet: ${objectMapper.writeValueAsString(journalpost)}") + fun hasAccessToJournalpostId(journalpostId: String): Boolean { + val journalpost = safGraphQlClient.getJournalpost(journalpostId) + securelog.info("journalpostid $journalpostId ble hentet: ${objectMapper.writeValueAsString(journalpost)}") - val id = - when (journalpost.journalpost?.bruker?.type) { - Type.ORGNR -> null - else -> journalpost.journalpost?.bruker?.id + val id = + when (journalpost.journalpost?.bruker?.type) { + Type.ORGNR -> null + else -> journalpost.journalpost?.bruker?.id + } + + if (id == null) { + securelog.info("Fant ikke id i journalpost: $journalpostId") + return false } - if (id == null) { - securelog.info("Fant ikke id i journalpost: $journalpostId") - return false + val fnr = personService.getPerson(id, journalpostId).fnr + + securelog.info("Fødselsnummer: $fnr") + val navEmail = nasjonalCommonService.getNavEmail() + val tilgang = hasAccess(fnr, journalpostId) + securelog.info("Innlogget bruker: $navEmail har${if (!tilgang) " ikke" else ""} til journalpost med id $journalpostId") + return tilgang } - val fnr = personService.getPerson(id, journalpostId).fnr + private fun hasAccess( + fnr: String, + navEmail: String, + ): Boolean { + val tilgang = istilgangskontrollOboClient.sjekkTilgangVeileder(fnr) + auditlog.info( + AuditLogger().createcCefMessage( + fnr = fnr, + navEmail = navEmail, + operation = AuditLogger.Operation.READ, + requestPath = "/api/graphql", + permit = + when (tilgang) { + true -> AuditLogger.Permit.PERMIT + false -> AuditLogger.Permit.DENY + }, + ), + ) - securelog.info("Fødselsnummer: $fnr") - val navEmail = getNavEmail() - val tilgang = hasAccess(fnr, journalpostId) - securelog.info("Innlogget bruker: $navEmail har${if (!tilgang) " ikke" else ""} til journalpost med id $journalpostId") - return tilgang - } + return tilgang + } - private fun hasAccess( - fnr: String, - navEmail: String, - ): Boolean { - val tilgang = istilgangskontrollOboClient.sjekkTilgangVeileder(fnr) - auditlog.info( - AuditLogger().createcCefMessage( - fnr = fnr, - navEmail = navEmail, - operation = AuditLogger.Operation.READ, - requestPath = "/api/graphql", - permit = - when (tilgang) { - true -> AuditLogger.Permit.PERMIT - false -> AuditLogger.Permit.DENY - }, - ), - ) - return tilgang - } } -fun getNavEmail(): String { - val authentication = SecurityContextHolder.getContext().authentication as JwtAuthenticationToken - return authentication.token.claims["preferred_username"].toString() -} + diff --git a/src/main/kotlin/no/nav/sykdig/model/SDSykmelding.kt b/src/main/kotlin/no/nav/sykdig/model/SDSykmelding.kt index 184dd194..a8c6487b 100644 --- a/src/main/kotlin/no/nav/sykdig/model/SDSykmelding.kt +++ b/src/main/kotlin/no/nav/sykdig/model/SDSykmelding.kt @@ -1,13 +1,13 @@ package no.nav.sykdig.model -import no.nav.sykdig.digitalisering.sykmelding.Arbeidsgiver -import no.nav.sykdig.digitalisering.sykmelding.Behandler -import no.nav.sykdig.digitalisering.sykmelding.KontaktMedPasient -import no.nav.sykdig.digitalisering.sykmelding.MedisinskVurdering -import no.nav.sykdig.digitalisering.sykmelding.MeldingTilNAV -import no.nav.sykdig.digitalisering.sykmelding.Periode -import no.nav.sykdig.digitalisering.sykmelding.Prognose -import no.nav.sykdig.digitalisering.sykmelding.SporsmalSvar +import no.nav.sykdig.digitalisering.felles.Arbeidsgiver +import no.nav.sykdig.digitalisering.felles.Behandler +import no.nav.sykdig.digitalisering.felles.KontaktMedPasient +import no.nav.sykdig.digitalisering.felles.MedisinskVurdering +import no.nav.sykdig.digitalisering.felles.MeldingTilNAV +import no.nav.sykdig.digitalisering.felles.Periode +import no.nav.sykdig.digitalisering.felles.Prognose +import no.nav.sykdig.digitalisering.felles.SporsmalSvar import java.time.LocalDate import java.time.OffsetDateTime diff --git a/src/main/kotlin/no/nav/sykdig/utils/DateTime.kt b/src/main/kotlin/no/nav/sykdig/utils/DateTime.kt new file mode 100644 index 00000000..b821575a --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/utils/DateTime.kt @@ -0,0 +1,18 @@ +package no.nav.sykdig.utils + +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.time.ZoneId +import java.time.ZoneOffset +import java.time.format.DateTimeParseException + +fun getLocalDateTime(dateTime: String): LocalDateTime { + return try { + OffsetDateTime.parse(dateTime).atZoneSameInstant(ZoneOffset.UTC).toLocalDateTime() + } catch (ex: DateTimeParseException) { + LocalDateTime.parse(dateTime) + .atZone(ZoneId.of("Europe/Oslo")) + .withZoneSameInstant(ZoneOffset.UTC) + .toLocalDateTime() + } +} diff --git a/src/main/kotlin/no/nav/sykdig/utils/NasjonalSykmeldingFellesformatMapper.kt b/src/main/kotlin/no/nav/sykdig/utils/NasjonalSykmeldingFellesformatMapper.kt new file mode 100644 index 00000000..38b7b986 --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/utils/NasjonalSykmeldingFellesformatMapper.kt @@ -0,0 +1,523 @@ +package no.nav.sykdig.utils + +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.LocalTime +import java.util.stream.Collectors +import no.nav.helse.eiFellesformat.XMLEIFellesformat +import no.nav.helse.msgHead.XMLCS +import no.nav.helse.msgHead.XMLCV +import no.nav.helse.msgHead.XMLDocument +import no.nav.helse.msgHead.XMLHealthcareProfessional +import no.nav.helse.msgHead.XMLIdent +import no.nav.helse.msgHead.XMLMsgHead +import no.nav.helse.msgHead.XMLMsgInfo +import no.nav.helse.msgHead.XMLOrganisation +import no.nav.helse.msgHead.XMLReceiver +import no.nav.helse.msgHead.XMLRefDoc +import no.nav.helse.msgHead.XMLSender +import no.nav.helse.sm2013.Address +import no.nav.helse.sm2013.ArsakType +import no.nav.helse.sm2013.CS +import no.nav.helse.sm2013.CV +import no.nav.helse.sm2013.DynaSvarType +import no.nav.helse.sm2013.HelseOpplysningerArbeidsuforhet +import no.nav.helse.sm2013.Ident +import no.nav.helse.sm2013.NavnType +import no.nav.helse.sm2013.TeleCom +import no.nav.helse.sm2013.URL +import no.nav.sykdig.applog +import no.nav.sykdig.digitalisering.felles.* +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.* +import no.nav.sykdig.digitalisering.pdl.Person +import no.nav.sykdig.digitalisering.pdl.client.graphql.PdlPerson +import no.nav.sykdig.securelog + + +fun mapsmRegistreringManuelltTilFellesformat( + smRegistreringManuell: SmRegistreringManuell, + pdlPasient: Person, + sykmelder: Sykmelder, + sykmeldingId: String, + datoOpprettet: LocalDateTime?, + journalpostId: String, +): XMLEIFellesformat { + return XMLEIFellesformat().apply { + any.add( + XMLMsgHead().apply { + msgInfo = + XMLMsgInfo().apply { + type = + XMLCS().apply { + dn = "Medisinsk vurdering av arbeidsmulighet ved sykdom, sykmelding" + v = "SYKMELD" + } + miGversion = "v1.2 2006-05-24" + genDate = + datoOpprettet?.toString() + ?: LocalDateTime.of( + smRegistreringManuell.perioder.first().fom, + LocalTime.NOON + ) + .toString() + msgId = sykmeldingId + ack = + XMLCS().apply { + dn = "Ja" + v = "J" + } + sender = + XMLSender().apply { + comMethod = + XMLCS().apply { + dn = "EDI" + v = "EDI" + } + organisation = + XMLOrganisation().apply { + healthcareProfessional = + XMLHealthcareProfessional().apply { + givenName = sykmelder.fornavn + middleName = sykmelder.mellomnavn + familyName = sykmelder.etternavn + ident.addAll( + listOf( + XMLIdent().apply { + id = sykmelder.fnr + typeId = + XMLCV().apply { + dn = "Fødselsnummer" + s = "2.16.578.1.12.4.1.1.8116" + v = "FNR" + } + }, + ), + ) + } + } + } + receiver = + XMLReceiver().apply { + comMethod = + XMLCS().apply { + dn = "EDI" + v = "EDI" + } + organisation = + XMLOrganisation().apply { + organisationName = "NAV" + ident.addAll( + listOf( + XMLIdent().apply { + id = "79768" + typeId = + XMLCV().apply { + dn = + "Identifikator fra Helsetjenesteenhetsregisteret (HER-id)" + s = "2.16.578.1.12.4.1.1.9051" + v = "HER" + } + }, + XMLIdent().apply { + id = "889640782" + typeId = + XMLCV().apply { + dn = + "Organisasjonsnummeret i Enhetsregister (Brønøysund)" + s = "2.16.578.1.12.4.1.1.9051" + v = "ENH" + } + }, + ), + ) + } + } + } + document.add( + XMLDocument().apply { + refDoc = + XMLRefDoc().apply { + msgType = + XMLCS().apply { + dn = "XML-instans" + v = "XML" + } + content = + XMLRefDoc.Content().apply { + any.add( + HelseOpplysningerArbeidsuforhet().apply { + syketilfelleStartDato = + tilSyketilfelleStartDato(smRegistreringManuell) + pasient = + HelseOpplysningerArbeidsuforhet.Pasient() + .apply { + navn = + NavnType().apply { + fornavn = + pdlPasient.navn.fornavn + mellomnavn = + pdlPasient.navn.mellomnavn + etternavn = + pdlPasient.navn.etternavn + } + fodselsnummer = + Ident().apply { + id = pdlPasient.fnr + typeId = + CV().apply { + dn = "Fødselsnummer" + s = + "2.16.578.1.12.4.1.1.8116" + v = "FNR" + } + } + } + arbeidsgiver = + tilArbeidsgiver( + smRegistreringManuell.arbeidsgiver + ) + medisinskVurdering = + tilMedisinskVurdering( + smRegistreringManuell.medisinskVurdering, + smRegistreringManuell.skjermesForPasient, + ) + aktivitet = + HelseOpplysningerArbeidsuforhet.Aktivitet() + .apply { + periode.addAll( + tilPeriodeListe( + smRegistreringManuell.perioder + ) + ) + } + prognose = null + utdypendeOpplysninger = + if ( + smRegistreringManuell + .harUtdypendeOpplysninger + ) + flaggScanHarUtdypendeOpplysninger() + else null + tiltak = null + meldingTilNav = + HelseOpplysningerArbeidsuforhet.MeldingTilNav() + .apply { + isBistandNAVUmiddelbart = + smRegistreringManuell.meldingTilNAV + ?.bistandUmiddelbart + ?: false + beskrivBistandNAV = + smRegistreringManuell.meldingTilNAV + ?.beskrivBistand + ?: "" + } + meldingTilArbeidsgiver = + smRegistreringManuell.meldingTilArbeidsgiver + kontaktMedPasient = + HelseOpplysningerArbeidsuforhet + .KontaktMedPasient() + .apply { + kontaktDato = + smRegistreringManuell + .kontaktMedPasient + .kontaktDato + begrunnIkkeKontakt = + smRegistreringManuell + .kontaktMedPasient + .begrunnelseIkkeKontakt + behandletDato = + LocalDateTime.of( + smRegistreringManuell + .behandletDato, + LocalTime.NOON + ) + } + behandler = tilBehandler(sykmelder) + avsenderSystem = + HelseOpplysningerArbeidsuforhet.AvsenderSystem() + .apply { + systemNavn = "Papirsykmelding" + systemVersjon = + journalpostId // Dette er nødvendig + // for at vi skal + // slippe å opprette + // generert PDF for + // papirsykmeldinger i + // syfosmsak + } + strekkode = "123456789qwerty" + }, + ) + } + } + }, + ) + }, + ) + } +} + +fun tilSyketilfelleStartDato(smRegistreringManuell: SmRegistreringManuell): LocalDate { + // Bruk innsendt syketilfelleStartDato, eller fall tilbake til dato fra perioder hvis ikke satt + return smRegistreringManuell.syketilfelleStartDato + ?: smRegistreringManuell.perioder.stream().map(Periode::fom).min(LocalDate::compareTo).get() +} + +fun tilBehandler(sykmelder: Sykmelder): HelseOpplysningerArbeidsuforhet.Behandler = + HelseOpplysningerArbeidsuforhet.Behandler().apply { + navn = + NavnType().apply { + fornavn = sykmelder.fornavn + mellomnavn = sykmelder.mellomnavn + etternavn = sykmelder.etternavn + } + id.addAll( + listOf( + Ident().apply { + id = sykmelder.fnr + typeId = + CV().apply { + dn = "Fødselsnummer" + s = "2.16.578.1.12.4.1.1.8116" + v = "FNR" + } + }, + Ident().apply { + id = sykmelder.hprNummer + typeId = + CV().apply { + dn = "HPR-nummer" + s = "2.16.578.1.12.4.1.1.8116" + v = "HPR" + } + }, + ), + ) + adresse = Address() + kontaktInfo.add( + TeleCom().apply { + typeTelecom = + CS().apply { + v = "HP" + dn = "Hovedtelefon" + } + teleAddress = URL().apply { v = "tel:55553336" } + }, + ) + } + +fun flaggScanHarUtdypendeOpplysninger(): HelseOpplysningerArbeidsuforhet.UtdypendeOpplysninger { + return HelseOpplysningerArbeidsuforhet.UtdypendeOpplysninger().apply { + spmGruppe.add( + HelseOpplysningerArbeidsuforhet.UtdypendeOpplysninger.SpmGruppe().apply { + spmGruppeId = "6.1" + spmGruppeTekst = "Utdypende opplysninger ved 7/8,17 og 39 uker" + spmSvar.add( + DynaSvarType().apply { + spmTekst = "Utdypende opplysninger" + restriksjon = + DynaSvarType.Restriksjon().apply { + restriksjonskode.add( + CS().apply { + dn = RestrictionCode.RESTRICTED_FOR_EMPLOYER.text + v = RestrictionCode.RESTRICTED_FOR_EMPLOYER.codeValue + }, + ) + } + spmId = "6.1.1" + svarTekst = "Papirsykmeldingen inneholder utdypende opplysninger." + }, + ) + }, + ) + } +} + +fun tilPeriodeListe( + perioder: List +): List { + return ArrayList().apply { + addAll( + perioder.map { tilHelseOpplysningerArbeidsuforhetPeriode(it) }, + ) + } +} + +fun tilHelseOpplysningerArbeidsuforhetPeriode( + periode: Periode +): HelseOpplysningerArbeidsuforhet.Aktivitet.Periode = + HelseOpplysningerArbeidsuforhet.Aktivitet.Periode().apply { + periodeFOMDato = periode.fom + periodeTOMDato = periode.tom + aktivitetIkkeMulig = + if (periode.aktivitetIkkeMulig != null) { + HelseOpplysningerArbeidsuforhet.Aktivitet.Periode.AktivitetIkkeMulig().apply { + medisinskeArsaker = + if (periode.aktivitetIkkeMulig.medisinskArsak != null) { + ArsakType().apply { + beskriv = periode.aktivitetIkkeMulig.medisinskArsak.beskrivelse + arsakskode.addAll( + periode.aktivitetIkkeMulig.medisinskArsak.arsak + .stream() + .map { + CS().apply { + v = it.codeValue + dn = it.text + } + } + .collect(Collectors.toList()), + ) + } + } else { + null + } + arbeidsplassen = + if (periode.aktivitetIkkeMulig.arbeidsrelatertArsak != null) { + ArsakType().apply { + beskriv = + periode.aktivitetIkkeMulig.arbeidsrelatertArsak.beskrivelse + arsakskode.addAll( + periode.aktivitetIkkeMulig.arbeidsrelatertArsak.arsak + .stream() + .map { + CS().apply { + v = it.codeValue + dn = it.text + } + } + .collect(Collectors.toList()), + ) + } + } else { + null + } + } + } else { + null + } + avventendeSykmelding = + if (periode.avventendeInnspillTilArbeidsgiver != null) { + HelseOpplysningerArbeidsuforhet.Aktivitet.Periode.AvventendeSykmelding().apply { + innspillTilArbeidsgiver = periode.avventendeInnspillTilArbeidsgiver + } + } else { + null + } + + gradertSykmelding = + if (periode.gradert != null) { + HelseOpplysningerArbeidsuforhet.Aktivitet.Periode.GradertSykmelding().apply { + sykmeldingsgrad = periode.gradert.grad + isReisetilskudd = periode.gradert.reisetilskudd + } + } else { + null + } + + behandlingsdager = + periode.behandlingsdager?.let { behandlingsdager -> + HelseOpplysningerArbeidsuforhet.Aktivitet.Periode.Behandlingsdager().apply { + antallBehandlingsdagerUke = behandlingsdager + } + } + + isReisetilskudd = periode.reisetilskudd + } + +fun tilArbeidsgiver(arbeidsgiver: Arbeidsgiver): HelseOpplysningerArbeidsuforhet.Arbeidsgiver = + HelseOpplysningerArbeidsuforhet.Arbeidsgiver().apply { + harArbeidsgiver = + when { + arbeidsgiver.harArbeidsgiver == HarArbeidsgiver.EN_ARBEIDSGIVER -> + CS().apply { + dn = "Én arbeidsgiver" + v = "1" + } + arbeidsgiver.harArbeidsgiver == HarArbeidsgiver.FLERE_ARBEIDSGIVERE -> + CS().apply { + dn = "Flere arbeidsgivere" + v = "2" + } + arbeidsgiver.harArbeidsgiver == HarArbeidsgiver.INGEN_ARBEIDSGIVER -> + CS().apply { + dn = "Ingen arbeidsgiver" + v = "3" + } + else -> { + val log = applog() + log.error("Arbeidsgiver type er ukjent, skal ikke kunne skje") + throw RuntimeException("Arbeidsgiver type er ukjent, skal ikke kunne skje") + } + } + + navnArbeidsgiver = arbeidsgiver.navn + yrkesbetegnelse = arbeidsgiver.yrkesbetegnelse + stillingsprosent = arbeidsgiver.stillingsprosent + } + +fun tilMedisinskVurdering( + medisinskVurdering: MedisinskVurdering, + skjermesForPasient: Boolean, +): HelseOpplysningerArbeidsuforhet.MedisinskVurdering { + val biDiagnoseListe: List = + medisinskVurdering.biDiagnoser.map { toMedisinskVurderingDiagnode(it) } + + return HelseOpplysningerArbeidsuforhet.MedisinskVurdering().apply { + if (medisinskVurdering.hovedDiagnose != null) { + hovedDiagnose = + HelseOpplysningerArbeidsuforhet.MedisinskVurdering.HovedDiagnose().apply { + diagnosekode = toMedisinskVurderingDiagnode(medisinskVurdering.hovedDiagnose) + } + } + if (biDiagnoseListe.isNotEmpty()) { + biDiagnoser = + HelseOpplysningerArbeidsuforhet.MedisinskVurdering.BiDiagnoser().apply { + diagnosekode.addAll(biDiagnoseListe) + } + } + isSkjermesForPasient = skjermesForPasient + annenFraversArsak = + medisinskVurdering.annenFraversArsak?.let { + ArsakType().apply { + arsakskode.addAll( + medisinskVurdering.annenFraversArsak.grunn + .stream() + .map { + CS().apply { + v = it.codeValue + dn = it.text + } + } + .collect(Collectors.toList()), + ) + beskriv = medisinskVurdering.annenFraversArsak.beskrivelse + } + } + isSvangerskap = medisinskVurdering.svangerskap + isYrkesskade = medisinskVurdering.yrkesskade + yrkesskadeDato = medisinskVurdering.yrkesskadeDato + } +} + +fun toMedisinskVurderingDiagnode(diagnose: Diagnose): CV = + CV().apply { + s = diagnose.system + v = diagnose.kode + dn = diagnose.tekst + } + +enum class RestrictionCode( + override val codeValue: String, + override val text: String, + override val oid: String = "2.16.578.1.12.4.1.1.8134" +) : Kodeverk { + RESTRICTED_FOR_EMPLOYER("A", "Informasjonen skal ikke vises arbeidsgiver"), + RESTRICTED_FOR_PATIENT("P", "Informasjonen skal ikke vises pasient"), + RESTRICTED_FOR_NAV("N", "Informasjonen skal ikke vises NAV"), +} + +interface Kodeverk { + val codeValue: String + val text: String + val oid: String +} diff --git a/src/main/kotlin/no/nav/sykdig/utils/NasjonalSykmeldingMapper.kt b/src/main/kotlin/no/nav/sykdig/utils/NasjonalSykmeldingMapper.kt new file mode 100644 index 00000000..74becf3a --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/utils/NasjonalSykmeldingMapper.kt @@ -0,0 +1,200 @@ +package no.nav.syfo.service + +import java.time.LocalDateTime +import no.nav.helse.sm2013.Address +import no.nav.helse.sm2013.ArsakType +import no.nav.helse.sm2013.CS +import no.nav.helse.sm2013.CV +import no.nav.helse.sm2013.HelseOpplysningerArbeidsuforhet +import no.nav.sykdig.digitalisering.felles.* +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.* + +fun HelseOpplysningerArbeidsuforhet.toSykmelding( + sykmeldingId: String, + pasientAktoerId: String, + legeAktoerId: String, + msgId: String, + signaturDato: LocalDateTime, +) = + Sykmelding( + id = sykmeldingId, + msgId = msgId, + pasientAktoerId = pasientAktoerId, + medisinskVurdering = medisinskVurdering.toMedisinskVurdering(), + skjermesForPasient = medisinskVurdering?.isSkjermesForPasient ?: false, + arbeidsgiver = arbeidsgiver.toArbeidsgiver(), + perioder = + aktivitet.periode.map(HelseOpplysningerArbeidsuforhet.Aktivitet.Periode::toPeriode), + prognose = null, + utdypendeOpplysninger = + if (utdypendeOpplysninger != null) utdypendeOpplysninger.toMap() else emptyMap(), + tiltakArbeidsplassen = null, + tiltakNAV = null, + andreTiltak = null, + meldingTilNAV = meldingTilNav?.toMeldingTilNAV(), + meldingTilArbeidsgiver = meldingTilArbeidsgiver, + kontaktMedPasient = kontaktMedPasient.toKontaktMedPasient(), + behandletTidspunkt = kontaktMedPasient.behandletDato, + behandler = behandler.toBehandler(legeAktoerId), + avsenderSystem = avsenderSystem.toAvsenderSystem(), + syketilfelleStartDato = syketilfelleStartDato, + signaturDato = signaturDato, + navnFastlege = pasient?.navnFastlege, + ) + +fun HelseOpplysningerArbeidsuforhet.Aktivitet.Periode.toPeriode() = + Periode( + fom = periodeFOMDato, + tom = periodeTOMDato, + aktivitetIkkeMulig = aktivitetIkkeMulig?.toAktivitetIkkeMulig(), + avventendeInnspillTilArbeidsgiver = avventendeSykmelding?.innspillTilArbeidsgiver, + behandlingsdager = behandlingsdager?.antallBehandlingsdagerUke, + gradert = gradertSykmelding?.toGradert(), + reisetilskudd = isReisetilskudd == true, + ) + +fun HelseOpplysningerArbeidsuforhet.Aktivitet.Periode.GradertSykmelding.toGradert() = + Gradert( + reisetilskudd = isReisetilskudd == true, + grad = sykmeldingsgrad, + ) + +fun HelseOpplysningerArbeidsuforhet.Arbeidsgiver.toArbeidsgiver() = + Arbeidsgiver( + harArbeidsgiver = HarArbeidsgiver.entries.first { it.codeValue == harArbeidsgiver.v }, + navn = navnArbeidsgiver, + yrkesbetegnelse = yrkesbetegnelse, + stillingsprosent = stillingsprosent, + ) + +fun HelseOpplysningerArbeidsuforhet.Aktivitet.Periode.AktivitetIkkeMulig.toAktivitetIkkeMulig() = + AktivitetIkkeMulig( + medisinskArsak = medisinskeArsaker?.toMedisinskArsak(), + arbeidsrelatertArsak = arbeidsplassen?.toArbeidsrelatertArsak(), + ) + +fun HelseOpplysningerArbeidsuforhet.MedisinskVurdering.toMedisinskVurdering() = + MedisinskVurdering( + hovedDiagnose = hovedDiagnose?.diagnosekode?.toDiagnose(), + biDiagnoser = biDiagnoser?.diagnosekode?.map(CV::toDiagnose) ?: listOf(), + svangerskap = isSvangerskap == true, + yrkesskade = isYrkesskade == true, + yrkesskadeDato = yrkesskadeDato, + annenFraversArsak = annenFraversArsak?.toAnnenFraversArsak(), + ) + +fun CV.toDiagnose() = Diagnose(s, v, dn) + +fun ArsakType.toAnnenFraversArsak() = + AnnenFraversArsak( + beskrivelse = beskriv, + // TODO: Remove if-wrapping whenever the EPJ systems stops sending garbage data + grunn = + arsakskode.mapNotNull { code -> + if (code.v == null || code.v == "0") { + null + } else { + AnnenFraverGrunn.entries.first { it.codeValue == code.v.trim() } + } + }, + ) + +// TODO: Remove if-wrapping whenever the EPJ systems stops sending garbage data +fun CS.toMedisinskArsakType() = + if (v == null || v == "0") { + null + } else { + MedisinskArsakType.entries.first { it.codeValue == v.trim() } + } + +// TODO: Remove if-wrapping whenever the EPJ systems stops sending garbage data +fun CS.toArbeidsrelatertArsakType() = + if (v == null || v == "0") { + null + } else { + ArbeidsrelatertArsakType.entries.first { it.codeValue == v } + } + +// TODO: Remove mapNotNull whenever the EPJ systems stops sending garbage data +fun HelseOpplysningerArbeidsuforhet.UtdypendeOpplysninger.toMap() = + spmGruppe + .map { spmGruppe -> + spmGruppe.spmGruppeId to + spmGruppe.spmSvar + .map { svar -> + svar.spmId to + SporsmalSvar( + sporsmal = svar.spmTekst, + svar = svar.svarTekst, + restriksjoner = + svar.restriksjon + ?.restriksjonskode + ?.mapNotNull(CS::toSvarRestriksjon) + ?: listOf() + ) + } + .toMap() + } + .toMap() + +// TODO: Remove if-wrapping whenever the EPJ systems stops sending garbage data +fun CS.toSvarRestriksjon() = + if (v.isNullOrBlank()) { + null + } else { + SvarRestriksjon.entries.first { it.codeValue == v } + } + +fun Address.toAdresse() = + Adresse( + gate = streetAdr, + postnummer = postalCode?.toIntOrNull(), + kommune = city, + postboks = postbox, + land = country?.v, + ) + +// TODO: Remove mapNotNull whenever the EPJ systems stops sending garbage data +fun ArsakType.toArbeidsrelatertArsak() = + ArbeidsrelatertArsak( + beskrivelse = beskriv, + arsak = arsakskode.mapNotNull(CS::toArbeidsrelatertArsakType), + ) + +// TODO: Remove mapNotNull whenever the EPJ systems stops sending garbage data +fun ArsakType.toMedisinskArsak() = + MedisinskArsak( + beskrivelse = beskriv, + arsak = arsakskode.mapNotNull(CS::toMedisinskArsakType), + ) + +fun HelseOpplysningerArbeidsuforhet.MeldingTilNav.toMeldingTilNAV() = + MeldingTilNAV( + bistandUmiddelbart = isBistandNAVUmiddelbart, + beskrivBistand = beskrivBistandNAV, + ) + +fun HelseOpplysningerArbeidsuforhet.KontaktMedPasient.toKontaktMedPasient() = + KontaktMedPasient( + kontaktDato = kontaktDato, + begrunnelseIkkeKontakt = begrunnIkkeKontakt, + ) + +fun HelseOpplysningerArbeidsuforhet.Behandler.toBehandler(aktoerId: String) = + Behandler( + fornavn = navn.fornavn, + mellomnavn = navn.mellomnavn, + etternavn = navn.etternavn, + aktoerId = aktoerId, + fnr = id.find { it.typeId.v == "FNR" }?.id ?: id.find { it.typeId.v == "DNR" }?.id!!, + hpr = id.find { it.typeId.v == "HPR" }?.id, + her = id.find { it.typeId.v == "HER" }?.id, + adresse = adresse.toAdresse(), + tlf = kontaktInfo.firstOrNull()?.teleAddress?.v, + ) + +fun HelseOpplysningerArbeidsuforhet.AvsenderSystem.toAvsenderSystem() = + AvsenderSystem( + navn = systemNavn, + versjon = systemVersjon, + ) diff --git a/src/main/kotlin/no/nav/sykdig/utils/RuleInfoExt.kt b/src/main/kotlin/no/nav/sykdig/utils/RuleInfoExt.kt new file mode 100644 index 00000000..21eddb89 --- /dev/null +++ b/src/main/kotlin/no/nav/sykdig/utils/RuleInfoExt.kt @@ -0,0 +1,12 @@ +package no.nav.sykdig.utils + +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.WhitelistedRuleHit +import no.nav.sykdig.digitalisering.sykmelding.RuleInfo + +fun List.isWhitelisted(): Boolean { + return this.all { (ruleName) -> + val isWhiteListed = + enumValues().any { enumValue -> enumValue.name == ruleName } + isWhiteListed + } +} diff --git a/src/main/kotlin/no/nav/sykdig/utils/Titel.kt b/src/main/kotlin/no/nav/sykdig/utils/Title.kt similarity index 80% rename from src/main/kotlin/no/nav/sykdig/utils/Titel.kt rename to src/main/kotlin/no/nav/sykdig/utils/Title.kt index a5d47e11..08f8f8f6 100644 --- a/src/main/kotlin/no/nav/sykdig/utils/Titel.kt +++ b/src/main/kotlin/no/nav/sykdig/utils/Title.kt @@ -1,6 +1,6 @@ package no.nav.sykdig.utils -import no.nav.sykdig.digitalisering.sykmelding.Periode +import no.nav.sykdig.digitalisering.felles.Periode import java.time.LocalDate import java.time.format.DateTimeFormatter @@ -30,6 +30,15 @@ fun createTitle( } } +fun createTitleNasjonal( + perioder: List?, + avvist: Boolean, +): String { + if (avvist && perioder != null) return "Avvist papirsykmelding ${getFomTomTekst(perioder)}" + if (perioder == null) return "Papirsykmelding" + return "Papirsykmelding ${getFomTomTekst(perioder)}" +} + fun createTitleNavNo( perioder: List?, avvisningsGrunn: String?, @@ -45,7 +54,7 @@ fun createTitleNavNo( private fun getFomTomTekst(perioder: List) = "${formaterDato(perioder.sortedSykmeldingPeriodeFOMDate().first().fom)} -" + - " ${formaterDato(perioder.sortedSykmeldingPeriodeTOMDate().last().tom)}" + " ${formaterDato(perioder.sortedSykmeldingPeriodeTOMDate().last().tom)}" fun List.sortedSykmeldingPeriodeFOMDate(): List = sortedBy { it.fom } diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 5a3ddae4..aa87f459 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -56,6 +56,8 @@ dokarkiv.url: ${DOKARKIV_URL} oppgave.url: ${OPPGAVE_URL} smregistrering.url: ${SMREGISTRERING_URL} helsenett.url: ${HELSENETT_URL} +smtss.url: ${SMTSS_URL} +regel.url: ${SYFOSMPAPIRREGLER_URL} no.nav.security.jwt: @@ -81,6 +83,22 @@ no.nav.security.jwt: client-id: ${AZURE_APP_CLIENT_ID} client-secret: ${AZURE_APP_CLIENT_SECRET} client-auth-method: client_secret_basic + kodeverk-m2m: + token-endpoint-url: ${AZURE_OPENID_CONFIG_TOKEN_ENDPOINT} + scope: api://${KODEVERK_CLIENT_ID}/.default + grant-type: client_credentials + authentication: + client-id: ${AZURE_APP_CLIENT_ID} + client-secret: ${AZURE_APP_CLIENT_SECRET} + client-auth-method: client_secret_basic + regel-m2m: + token-endpoint-url: ${AZURE_OPENID_CONFIG_TOKEN_ENDPOINT} + scope: api://${SYFOSMPAPIRREGLER_CLIENT_ID}/.default + grant-type: client_credentials + authentication: + client-id: ${AZURE_APP_CLIENT_ID} + client-secret: ${AZURE_APP_CLIENT_SECRET} + client-auth-method: client_secret_basic helsenett-m2m: token-endpoint-url: ${AZURE_OPENID_CONFIG_TOKEN_ENDPOINT} scope: api://${HELSENETT_CLIENT_ID}/.default @@ -89,9 +107,9 @@ no.nav.security.jwt: client-id: ${AZURE_APP_CLIENT_ID} client-secret: ${AZURE_APP_CLIENT_SECRET} client-auth-method: client_secret_basic - kodeverk-m2m: + smtss-m2m: token-endpoint-url: ${AZURE_OPENID_CONFIG_TOKEN_ENDPOINT} - scope: api://${KODEVERK_CLIENT_ID}/.default + scope: api://${SMTSS_CLIENT_ID}/.default grant-type: client_credentials authentication: client-id: ${AZURE_APP_CLIENT_ID} diff --git a/src/main/resources/db/migration/V14__create_nasjonal_tables.sql b/src/main/resources/db/migration/V14__create_nasjonal_tables.sql index 6dd91651..1d9b0593 100644 --- a/src/main/resources/db/migration/V14__create_nasjonal_tables.sql +++ b/src/main/resources/db/migration/V14__create_nasjonal_tables.sql @@ -1,6 +1,3 @@ -drop table if exists nasjonal_manuelloppgave; -drop table if exists nasjonal_sykmelding; - create table nasjonal_manuelloppgave ( id UUID DEFAULT gen_random_uuid() PRIMARY KEY, @@ -9,25 +6,30 @@ create table nasjonal_manuelloppgave fnr VARCHAR NULL, aktor_id VARCHAR NULL, dokument_info_id VARCHAR NULL, - dato_opprettet TIMESTAMP NULL, + dato_opprettet TIMESTAMP with time zone NULL, oppgave_id INT, ferdigstilt boolean NOT NULL, papir_sm_registrering JSONB NULL, utfall VARCHAR NULL, ferdigstilt_av VARCHAR NULL, - dato_ferdigstilt TIMESTAMP NULL, + dato_ferdigstilt TIMESTAMP with time zone NULL, avvisningsgrunn VARCHAR NULL ); create table nasjonal_sykmelding ( - sykmelding_id VARCHAR NOT NULL, + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + sykmelding_id VARCHAR NOT NULL, sykmelding JSONB NOT NULL, timestamp TIMESTAMP with time zone NOT NULL, - ferdigstilt_av VARCHAR null, - dato_ferdigstilt TIMESTAMP, - primary key (sykmelding_id, timestamp) + ferdigstilt_av VARCHAR NULL, + dato_ferdigstilt TIMESTAMP with time zone NULL ); ALTER TABLE nasjonal_manuelloppgave - ADD CONSTRAINT unique_sykmelding_id UNIQUE (sykmelding_id); \ No newline at end of file + ADD CONSTRAINT unique_sykmelding_id UNIQUE (sykmelding_id); + +ALTER TABLE nasjonal_sykmelding + ADD CONSTRAINT unique_sykmelding_id_timestamp UNIQUE (sykmelding_id, timestamp); + +CREATE INDEX idx_sykmelding_id ON sykmelding(sykmelding_id); diff --git a/src/main/resources/db/migration/V15__add_id_nasjonal_sykmelding.sql b/src/main/resources/db/migration/V15__add_id_nasjonal_sykmelding.sql deleted file mode 100644 index 2b4e05b3..00000000 --- a/src/main/resources/db/migration/V15__add_id_nasjonal_sykmelding.sql +++ /dev/null @@ -1,14 +0,0 @@ -drop table if exists nasjonal_sykmelding; - -create table nasjonal_sykmelding -( - id UUID DEFAULT gen_random_uuid() PRIMARY KEY, - sykmelding_id VARCHAR NOT NULL, - sykmelding JSONB NOT NULL, - timestamp TIMESTAMP with time zone NOT NULL, - ferdigstilt_av VARCHAR NULL, - dato_ferdigstilt TIMESTAMP NULL -); - -ALTER TABLE nasjonal_sykmelding - ADD CONSTRAINT unique_sykmelding_id_timestamp UNIQUE (sykmelding_id, timestamp); \ No newline at end of file diff --git a/src/main/resources/db/migration/V16__create_sykmelding_index.sql b/src/main/resources/db/migration/V16__create_sykmelding_index.sql deleted file mode 100644 index fad4ff19..00000000 --- a/src/main/resources/db/migration/V16__create_sykmelding_index.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE INDEX idx_sykmelding_id ON sykmelding(sykmelding_id); diff --git a/src/test/kotlin/no/nav/sykdig/IntegrationTest.kt b/src/test/kotlin/no/nav/sykdig/IntegrationTest.kt index 0c47f3bd..49d7687e 100644 --- a/src/test/kotlin/no/nav/sykdig/IntegrationTest.kt +++ b/src/test/kotlin/no/nav/sykdig/IntegrationTest.kt @@ -2,6 +2,7 @@ package no.nav.sykdig import no.nav.sykdig.db.OppgaveRepository import no.nav.sykdig.digitalisering.papirsykmelding.db.NasjonalOppgaveRepository +import no.nav.sykdig.digitalisering.papirsykmelding.db.NasjonalSykmeldingRepository import org.junit.jupiter.api.AfterAll import org.junit.jupiter.api.TestInstance import org.springframework.beans.factory.annotation.Autowired @@ -27,6 +28,9 @@ abstract class IntegrationTest { @Autowired lateinit var nasjonalOppgaveRepository: NasjonalOppgaveRepository + @Autowired + lateinit var nasjonalSykmeldingRepository: NasjonalSykmeldingRepository + @Autowired lateinit var oppgaveRepository: OppgaveRepository diff --git a/src/test/kotlin/no/nav/sykdig/digitalisering/SykDigOppgaveServiceTest.kt b/src/test/kotlin/no/nav/sykdig/digitalisering/SykDigOppgaveServiceTest.kt index 0f961137..a40e4bb8 100644 --- a/src/test/kotlin/no/nav/sykdig/digitalisering/SykDigOppgaveServiceTest.kt +++ b/src/test/kotlin/no/nav/sykdig/digitalisering/SykDigOppgaveServiceTest.kt @@ -94,7 +94,7 @@ class SykDigOppgaveServiceTest : IntegrationTest() { @Test fun ferdigstillerOppgaveIDb() { - sykDigOppgaveService.ferdigstillOppgave( + sykDigOppgaveService.ferdigstillUtenlandskAvvistOppgave( oppgave = createDigitalseringsoppgaveDbModel(oppgaveId = "123", fnr = "12345678910"), navEpost = "X987654", values = diff --git a/src/test/kotlin/no/nav/sykdig/digitalisering/UtenlandskOppgaveServiceTest.kt b/src/test/kotlin/no/nav/sykdig/digitalisering/UtenlandskOppgaveServiceTest.kt index 7d1b94bf..f9f4a426 100644 --- a/src/test/kotlin/no/nav/sykdig/digitalisering/UtenlandskOppgaveServiceTest.kt +++ b/src/test/kotlin/no/nav/sykdig/digitalisering/UtenlandskOppgaveServiceTest.kt @@ -7,7 +7,7 @@ import no.nav.sykdig.digitalisering.ferdigstilling.FerdigstillingService import no.nav.sykdig.digitalisering.ferdigstilling.GosysService import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.GetOppgaveResponse import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.OppgaveType -import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.Oppgavestatus +import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.OppgaveStatus import no.nav.sykdig.digitalisering.pdl.Navn import no.nav.sykdig.digitalisering.pdl.Person import no.nav.sykdig.digitalisering.pdl.PersonService @@ -82,7 +82,7 @@ class UtenlandskOppgaveServiceTest : IntegrationTest() { val oppgaveResponseMock = GetOppgaveResponse( versjon = 1, - status = Oppgavestatus.OPPRETTET, + status = OppgaveStatus.OPPRETTET, behandlesAvApplikasjon = "SMM", id = 1, tilordnetRessurs = "A123456", diff --git a/src/test/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/FerdigstillingServiceTest.kt b/src/test/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/FerdigstillingServiceTest.kt index f396a884..f5dee95b 100644 --- a/src/test/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/FerdigstillingServiceTest.kt +++ b/src/test/kotlin/no/nav/sykdig/digitalisering/ferdigstilling/FerdigstillingServiceTest.kt @@ -19,13 +19,14 @@ import no.nav.sykdig.digitalisering.saf.graphql.Journalstatus import no.nav.sykdig.digitalisering.saf.graphql.SafJournalpost import no.nav.sykdig.digitalisering.saf.graphql.SafQueryJournalpost import no.nav.sykdig.digitalisering.saf.graphql.TEMA_SYKMELDING -import no.nav.sykdig.digitalisering.sykmelding.AktivitetIkkeMulig -import no.nav.sykdig.digitalisering.sykmelding.AvsenderSystem -import no.nav.sykdig.digitalisering.sykmelding.Diagnose -import no.nav.sykdig.digitalisering.sykmelding.KontaktMedPasient -import no.nav.sykdig.digitalisering.sykmelding.Periode +import no.nav.sykdig.digitalisering.felles.AktivitetIkkeMulig +import no.nav.sykdig.digitalisering.felles.AvsenderSystem +import no.nav.sykdig.digitalisering.felles.Diagnose +import no.nav.sykdig.digitalisering.felles.KontaktMedPasient +import no.nav.sykdig.digitalisering.felles.Periode import no.nav.sykdig.digitalisering.sykmelding.ReceivedSykmelding -import no.nav.sykdig.digitalisering.sykmelding.SporsmalSvar +import no.nav.sykdig.digitalisering.felles.SporsmalSvar +import no.nav.sykdig.digitalisering.helsenett.SykmelderService import no.nav.sykdig.generated.types.DiagnoseInput import no.nav.sykdig.generated.types.PeriodeInput import no.nav.sykdig.generated.types.PeriodeType @@ -61,6 +62,9 @@ class FerdigstillingServiceTest : IntegrationTest() { @MockBean lateinit var oppgaveClient: OppgaveClient + @MockBean + lateinit var sykmelderService: SykmelderService + @Autowired lateinit var sykmeldingOKProducer: KafkaProducer @@ -72,11 +76,11 @@ class FerdigstillingServiceTest : IntegrationTest() { @BeforeEach fun setup() { ferdigstillingService = - FerdigstillingService(safJournalpostGraphQlClient, dokarkivClient, oppgaveClient, sykmeldingOKProducer, dokumentService) + FerdigstillingService(safJournalpostGraphQlClient, dokarkivClient, oppgaveClient, sykmeldingOKProducer, dokumentService, sykmelderService) } @Test - fun ferdigstillOppdatererDokarkivOppgaveOgTopic() { + fun ferdigstillUtenlandskOppgaveOppdatererDokarkivOppgaveOgTopic() { val sykmeldingId = UUID.randomUUID() val journalpostId = "9898" val dokumentInfoId = "111" @@ -153,7 +157,7 @@ class FerdigstillingServiceTest : IntegrationTest() { fodselsdato = LocalDate.of(1970, 1, 1), ) - ferdigstillingService.ferdigstill( + ferdigstillingService.ferdigstillUtenlandskOppgave( enhet = "2990", oppgave = createDigitalseringsoppgaveDbModel( @@ -167,7 +171,7 @@ class FerdigstillingServiceTest : IntegrationTest() { validatedValues = validatedValues, ) - verify(dokarkivClient).oppdaterOgFerdigstillJournalpost( + verify(dokarkivClient).oppdaterOgFerdigstillUtenlandskJournalpost( "SWE", "12345678910", "2990", diff --git a/src/test/kotlin/no/nav/sykdig/digitalisering/helsenett/SykmelderServiceTest.kt b/src/test/kotlin/no/nav/sykdig/digitalisering/helsenett/SykmelderServiceTest.kt index 6f378f88..2360b6c6 100644 --- a/src/test/kotlin/no/nav/sykdig/digitalisering/helsenett/SykmelderServiceTest.kt +++ b/src/test/kotlin/no/nav/sykdig/digitalisering/helsenett/SykmelderServiceTest.kt @@ -6,19 +6,22 @@ import io.mockk.mockk import kotlinx.coroutines.runBlocking import no.nav.sykdig.digitalisering.exceptions.SykmelderNotFoundException import no.nav.sykdig.digitalisering.helsenett.client.HelsenettClient +import no.nav.sykdig.digitalisering.helsenett.client.SmtssClient import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Godkjenning import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Kode import no.nav.sykdig.digitalisering.pdl.Navn import no.nav.sykdig.digitalisering.pdl.Person import no.nav.sykdig.digitalisering.pdl.PersonService +import no.nav.sykdig.digitalisering.pdl.client.PdlClient import org.amshove.kluent.internal.assertFailsWith import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test class SykmelderServiceTest { - private val pdlService = mockk() private val helsenettClient = mockk() - private val sykmelderService = SykmelderService(helsenettClient, pdlService) + private val personService = mockk() + private val smtssClient = mockk() + private val sykmelderService = SykmelderService(helsenettClient, personService, smtssClient) @Test fun `get sykmelder happy case`() { @@ -47,7 +50,7 @@ class SykmelderServiceTest { etternavn = etternavn ) - coEvery { pdlService.getPerson(any(), any()) } returns expectedPerson + coEvery { personService.getPerson(any(), any()) } returns expectedPerson coEvery { helsenettClient.getBehandler(hprNummer, "callid") } returns expectedBehandler val sykmelder = runBlocking { sykmelderService.getSykmelder(hprNummer, "callid") } @@ -58,7 +61,7 @@ class SykmelderServiceTest { assertEquals(mellomnavn, sykmelder.mellomnavn) assertEquals(etternavn, sykmelder.etternavn) - coVerify { pdlService.getPerson(any(), any()) } + coVerify { personService.getPerson(any(), any()) } coVerify { helsenettClient.getBehandler(hprNummer, "callid") } } @@ -79,7 +82,7 @@ class SykmelderServiceTest { fodselsdato = null ) - coEvery { pdlService.getPerson(any(), any()) } returns expectedPerson + coEvery { personService.getPerson(any(), any()) } returns expectedPerson coEvery { helsenettClient.getBehandler(hprNummer, "callid") } throws SykmelderNotFoundException("Kunne ikke hente fnr for hpr $hprNummer") val exception = runBlocking { assertFailsWith { sykmelderService.getSykmelder(hprNummer, "callid") } } diff --git a/src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveRepositoryTest.kt b/src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveRepositoryTest.kt index 86559c4a..f423e4d1 100644 --- a/src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveRepositoryTest.kt +++ b/src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveRepositoryTest.kt @@ -1,5 +1,7 @@ package no.nav.sykdig.digitalisering.papirsykmelding +import kotlinx.coroutines.flow.count +import kotlinx.coroutines.runBlocking import no.nav.sykdig.IntegrationTest import no.nav.sykdig.digitalisering.papirsykmelding.api.model.PapirSmRegistering import no.nav.sykdig.digitalisering.papirsykmelding.db.model.NasjonalManuellOppgaveDAO @@ -14,24 +16,24 @@ import java.util.UUID class NasjonalOppgaveRepositoryTest : IntegrationTest() { @Test - fun `given New NasjonalOppgave opprett og hent`() { + fun `given New NasjonalOppgave opprett og hent`() = runBlocking { val savedOppgave = nasjonalOppgaveRepository.save(testData(null, "123")) val retrievedOppgave = nasjonalOppgaveRepository.findBySykmeldingId(savedOppgave.sykmeldingId) - Assertions.assertTrue(retrievedOppgave.isPresent) - assertEquals(savedOppgave.sykmeldingId, retrievedOppgave.get().sykmeldingId) + Assertions.assertNotNull(retrievedOppgave) + assertEquals(savedOppgave.sykmeldingId, retrievedOppgave?.sykmeldingId) } @Test - fun `insert two instances with same sykmeldingId`() { + fun `insert two instances with same sykmeldingId`() = runBlocking { nasjonalOppgaveRepository.save(testData(null, "1")) val eksisterendeOppgave = nasjonalOppgaveRepository.findBySykmeldingId("1") - nasjonalOppgaveRepository.save(testData(eksisterendeOppgave.get().id, "1")) + nasjonalOppgaveRepository.save(testData(eksisterendeOppgave?.id, "1")) val retrievedOppgave = nasjonalOppgaveRepository.findAll() assertEquals(1, retrievedOppgave.count()) } @Test - fun `insert two instances with unique id`() { + fun `insert two instances with unique id`() = runBlocking { nasjonalOppgaveRepository.save(testData(null, "3")) nasjonalOppgaveRepository.save(testData(null, "4")) val retrievedOppgave = nasjonalOppgaveRepository.findAll() @@ -39,7 +41,7 @@ class NasjonalOppgaveRepositoryTest : IntegrationTest() { } @BeforeEach - fun setup() { + fun setup() = runBlocking { nasjonalOppgaveRepository.deleteAll() } diff --git a/src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveServiceTest.kt b/src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveServiceTest.kt index 4e47aff1..2200542b 100644 --- a/src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveServiceTest.kt +++ b/src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalOppgaveServiceTest.kt @@ -1,31 +1,142 @@ package no.nav.sykdig.digitalisering.papirsykmelding +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import kotlinx.coroutines.runBlocking import no.nav.sykdig.IntegrationTest +import no.nav.sykdig.digitalisering.SykDigOppgaveService +import no.nav.sykdig.digitalisering.dokarkiv.DokarkivClient +import no.nav.sykdig.digitalisering.dokument.DocumentService +import no.nav.sykdig.digitalisering.felles.Adresse +import no.nav.sykdig.digitalisering.felles.Behandler +import no.nav.sykdig.digitalisering.ferdigstilling.oppgave.OppgaveClient +import no.nav.sykdig.digitalisering.helsenett.SykmelderService +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.AvvisSykmeldingRequest import no.nav.sykdig.digitalisering.papirsykmelding.api.model.PapirManuellOppgave import no.nav.sykdig.digitalisering.papirsykmelding.api.model.PapirSmRegistering +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Sykmelder +import no.nav.sykdig.digitalisering.papirsykmelding.api.model.Veileder import no.nav.sykdig.digitalisering.papirsykmelding.db.model.NasjonalManuellOppgaveDAO +import no.nav.sykdig.digitalisering.pdl.Navn +import no.nav.sykdig.digitalisering.pdl.Person +import no.nav.sykdig.digitalisering.pdl.PersonService +import no.nav.sykdig.digitalisering.saf.SafJournalpostGraphQlClient +import no.nav.sykdig.digitalisering.saf.graphql.SafQueryJournalpost +import no.nav.sykdig.model.OppgaveDbModel import okhttp3.internal.EMPTY_BYTE_ARRAY import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Assertions.assertTrue import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mockito import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Qualifier +import org.springframework.boot.test.mock.mockito.MockBean +import org.springframework.http.HttpMethod +import org.springframework.http.HttpStatus +import org.springframework.http.MediaType +import org.springframework.security.core.context.ReactiveSecurityContextHolder +import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.security.oauth2.jwt.Jwt +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken import org.springframework.test.context.junit.jupiter.SpringExtension +import org.springframework.test.web.client.MockRestServiceServer +import org.springframework.test.web.client.match.MockRestRequestMatchers.method +import org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo +import org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess +import org.springframework.web.client.RestTemplate +import reactor.core.publisher.Mono import java.time.LocalDate import java.time.LocalDateTime import java.time.OffsetDateTime -import java.util.UUID +import java.util.* @ExtendWith(SpringExtension::class) class NasjonalOppgaveServiceTest : IntegrationTest() { @Autowired lateinit var nasjonalOppgaveService: NasjonalOppgaveService + val mapper = jacksonObjectMapper() + + @MockBean + lateinit var sykdigOppgaveService: SykDigOppgaveService + + @MockBean + lateinit var personService: PersonService + + @MockBean + lateinit var safJournalpostGraphQlClient: SafJournalpostGraphQlClient + + @MockBean + lateinit var dokarkivClient: DokarkivClient + + @MockBean + lateinit var sykmelderService: SykmelderService + + @MockBean + lateinit var oppgaveClient: OppgaveClient + + @MockBean + lateinit var documentService: DocumentService + + @MockBean + lateinit var nasjonaCommonService: NasjonalCommonService + + @Autowired + @Qualifier("smregisteringRestTemplate") + private lateinit var restTemplate: RestTemplate + @BeforeEach - fun setUp() { + fun setUp() = runBlocking { + mockJwtAuthentication() + val mockServer = MockRestServiceServer.createServer(restTemplate) + + mockServer.expect(requestTo("http://localhost:8081/azureator/token")) + .andExpect(method(HttpMethod.POST)) + .andRespond(withSuccess("{\"access_token\": \"dummy-token\"}", MediaType.APPLICATION_JSON)) + nasjonalOppgaveRepository.deleteAll() } + + @Test + fun `avvis oppgave blir oppdatert og lagra i DB`() { + val oppgaveId = "123" + val request = mapper.writeValueAsString(AvvisSykmeldingRequest(reason = "MANGLENDE_DIAGNOSE")) + val originalOppgave = nasjonalOppgaveService.lagreOppgave(testDataPapirManuellOppgave()) + + Mockito.`when`(sykdigOppgaveService.getOppgave(anyString())).thenReturn(testDataOppgaveDbModel(oppgaveId)) + + Mockito.`when`(nasjonaCommonService.getNavEmail()).thenReturn("navEmail") + Mockito.`when`(nasjonaCommonService.getNavIdent()).thenReturn(Veileder("navIdent")) + + Mockito.`when`(personService.getPerson(anyString(), anyString())).thenReturn(testDataPerson()) + Mockito.`when`(safJournalpostGraphQlClient.getJournalpost(anyString())).thenReturn(SafQueryJournalpost(null)) + + Mockito.`when`(safJournalpostGraphQlClient.erFerdigstilt(org.mockito.kotlin.any())).thenReturn(false) + Mockito.`when`(dokarkivClient.oppdaterOgFerdigstillNasjonalJournalpost( + journalpostId = org.mockito.kotlin.any(), + dokumentInfoId = org.mockito.kotlin.any(), + pasientFnr = org.mockito.kotlin.any(), + sykmeldingId = org.mockito.kotlin.any(), + sykmelder = org.mockito.kotlin.any(), + loggingMeta = org.mockito.kotlin.any(), + navEnhet = org.mockito.kotlin.any(), + avvist = org.mockito.kotlin.any(), + perioder = org.mockito.kotlin.any(), + )).thenReturn(null) + + Mockito.`when`(sykmelderService.getSykmelder(org.mockito.kotlin.any(), org.mockito.kotlin.any())).thenReturn(testDataSykmelder()) + Mockito.doNothing().`when`(oppgaveClient).ferdigstillOppgave(org.mockito.kotlin.any(), org.mockito.kotlin.any()) + Mockito.doNothing().`when`(documentService).updateDocumentTitle(org.mockito.kotlin.any(), org.mockito.kotlin.any(), org.mockito.kotlin.any()) + + assertTrue(originalOppgave.avvisningsgrunn == null) + val avvistOppgave = nasjonalOppgaveService.avvisOppgave(oppgaveId, request, "enhet", "auth") + assertEquals(avvistOppgave.statusCode, HttpStatus.NO_CONTENT) + } + + @Test fun `mapToDao der id er null`() { val dao = nasjonalOppgaveService.mapToDao(testDataPapirManuellOppgave(), null) @@ -44,12 +155,14 @@ class NasjonalOppgaveServiceTest : IntegrationTest() { } @Test - fun `oppgave isPresent`() { + fun `oppgave blir lagret`() = runBlocking { val uuid = UUID.randomUUID() - val dao = testDataNasjonalManuellOppgaveDAO(uuid, "123") + val dao = testDataNasjonalManuellOppgaveDAO(uuid, "123", 123) val oppgave = nasjonalOppgaveService.lagreOppgave(testDataPapirManuellOppgave()) assertEquals(oppgave.sykmeldingId, dao.sykmeldingId) + val res = nasjonalOppgaveRepository.findBySykmeldingId(oppgave.sykmeldingId) + println(res) } fun testDataPapirManuellOppgave(): PapirManuellOppgave { @@ -81,15 +194,36 @@ class NasjonalOppgaveServiceTest : IntegrationTest() { meldingTilArbeidsgiver = null, kontaktMedPasient = null, behandletTidspunkt = null, - behandler = null, + behandler = Behandler("fornavn", "mellomnavn", "etternavn", "", "", "", null, Adresse(null, null, null, null, null), null), ), documents = emptyList(), ) } + private fun testDataOppgaveDbModel(oppgaveId: String): OppgaveDbModel { + return OppgaveDbModel( + oppgaveId = oppgaveId.toString(), + fnr = "fnr", + journalpostId = "jpdId", + dokumentInfoId = "DokInfoId", + dokumenter = emptyList(), + opprettet = OffsetDateTime.now(), + ferdigstilt = null, + tilbakeTilGosys = false, + avvisingsgrunn = null, + sykmeldingId = UUID.randomUUID(), + type = "type", + sykmelding = null, + endretAv = "sakebehandler", + timestamp = OffsetDateTime.now(), + source = "source", + ) + } + fun testDataNasjonalManuellOppgaveDAO( id: UUID?, sykmeldingId: String, + oppgaveId: Int?, ): NasjonalManuellOppgaveDAO { return NasjonalManuellOppgaveDAO( id = id, @@ -99,7 +233,7 @@ class NasjonalOppgaveServiceTest : IntegrationTest() { aktorId = "aktor", dokumentInfoId = "123", datoOpprettet = LocalDateTime.now(), - oppgaveId = 123, + oppgaveId = oppgaveId, ferdigstilt = false, papirSmRegistrering = PapirSmRegistering( @@ -132,4 +266,45 @@ class NasjonalOppgaveServiceTest : IntegrationTest() { avvisningsgrunn = null, ) } + + fun testDataPerson(): Person { + return Person( + fnr = "fnr", + Navn( + fornavn = "fornavn", + etternavn = "etternavn", + mellomnavn = "mellomnavn" + ), + aktorId = "aktorId", + bostedsadresse = null, + oppholdsadresse = null, + fodselsdato = null + ) + } + + fun testDataSykmelder(): Sykmelder { + return Sykmelder( + hprNummer = "123", + fnr = "fnr", + aktorId = "aktorId", + fornavn = "fornavn", + mellomnavn = "mellomnavn", + etternavn = "etternavn", + godkjenninger = emptyList() + ) + } + + fun mockJwtAuthentication() { + val jwt = Jwt.withTokenValue("dummy-token") + .header("alg", "none") + .claim("sub", "test-user") + .claim("NAVident", "test-ident") + .claim("scope", "test-scope") + .build() + val authentication = JwtAuthenticationToken(jwt) + val securityContext = SecurityContextHolder.createEmptyContext() + securityContext.authentication = authentication + SecurityContextHolder.setContext(securityContext) + ReactiveSecurityContextHolder.withSecurityContext(Mono.just(securityContext)) + } } diff --git a/src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalSykmeldingRepositoryTest.kt b/src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalSykmeldingRepositoryTest.kt new file mode 100644 index 00000000..b5a789fb --- /dev/null +++ b/src/test/kotlin/no/nav/sykdig/digitalisering/papirsykmelding/NasjonalSykmeldingRepositoryTest.kt @@ -0,0 +1,136 @@ +package no.nav.sykdig.digitalisering.papirsykmelding + +import no.nav.sykdig.IntegrationTest +import no.nav.sykdig.digitalisering.felles.Adresse +import no.nav.sykdig.digitalisering.felles.AktivitetIkkeMulig +import no.nav.sykdig.digitalisering.felles.Arbeidsgiver +import no.nav.sykdig.digitalisering.felles.ArbeidsrelatertArsak +import no.nav.sykdig.digitalisering.felles.ArbeidsrelatertArsakType +import no.nav.sykdig.digitalisering.felles.AvsenderSystem +import no.nav.sykdig.digitalisering.felles.Behandler +import no.nav.sykdig.digitalisering.felles.Diagnose +import no.nav.sykdig.digitalisering.felles.HarArbeidsgiver +import no.nav.sykdig.digitalisering.felles.KontaktMedPasient +import no.nav.sykdig.digitalisering.felles.MedisinskVurdering +import no.nav.sykdig.digitalisering.felles.Periode +import no.nav.sykdig.digitalisering.felles.Sykmelding +import no.nav.sykdig.digitalisering.papirsykmelding.db.model.NasjonalSykmeldingDAO +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.OffsetDateTime +import java.util.* + +class NasjonalSykmeldingRepositoryTest : IntegrationTest() { + + @BeforeEach + fun setup() { + nasjonalSykmeldingRepository.deleteAll() + } + + @Test + fun `legg til og hent ny sykmelding`() { + val dao = testData(null, "123") + println(dao.sykmelding) + Assertions.assertNotNull(dao.sykmelding) + val res = nasjonalSykmeldingRepository.save(dao) + + val nasjonalSykmelding = nasjonalSykmeldingRepository.findBySykmeldingId(res.sykmeldingId) + Assertions.assertTrue(nasjonalSykmelding.isPresent) + } + + fun testData(id: UUID?, sykmeldingId: String): NasjonalSykmeldingDAO { + return NasjonalSykmeldingDAO( + id = id, + sykmeldingId = sykmeldingId, + sykmelding = sykmeldingTestData(sykmeldingId) , + timestamp = OffsetDateTime.now(), + ferdigstiltAv = "the saksbehandler", + datoFerdigstilt = LocalDateTime.now() + ) + } + + fun sykmeldingTestData(sykmeldingId: String): Sykmelding { + return Sykmelding( + id = sykmeldingId, + msgId = sykmeldingId, + pasientAktoerId = "123", + medisinskVurdering = MedisinskVurdering( + hovedDiagnose = Diagnose( + system = "foo", + kode = "bar", + tekst = null + ), + biDiagnoser = emptyList(), + svangerskap = false, + yrkesskade = false, + yrkesskadeDato = null, + annenFraversArsak = null + ), + skjermesForPasient = false, + arbeidsgiver = Arbeidsgiver( + harArbeidsgiver = HarArbeidsgiver.EN_ARBEIDSGIVER, + navn = null, + yrkesbetegnelse = null, + stillingsprosent = null + ), + perioder = perioderTestData(), + prognose = null, + utdypendeOpplysninger = emptyMap(), + tiltakArbeidsplassen = null, + tiltakNAV = null, + andreTiltak = null, + meldingTilNAV = null, + meldingTilArbeidsgiver = null, + kontaktMedPasient = KontaktMedPasient( + kontaktDato = null, + begrunnelseIkkeKontakt = null + ), + behandletTidspunkt = LocalDateTime.now(), + behandler = Behandler( + fornavn = "fornavn", + mellomnavn = null, + etternavn = "etternavn", + aktoerId = "456456456", + fnr = "98765432101", + hpr = null, + her = null, + adresse = Adresse( + gate = null, + postnummer = null, + kommune = null, + postboks = null, + land = null + ), + tlf = null + ), + avsenderSystem = AvsenderSystem( + navn = "avsenderSystem", + versjon = "1.0" + ), + syketilfelleStartDato = LocalDate.now(), + signaturDato = LocalDateTime.now(), + navnFastlege = "fastlegen" + ) + } + + fun perioderTestData(): List { + return listOf(Periode( + fom = LocalDate.now(), + tom = LocalDate.now().plusDays(3), + aktivitetIkkeMulig = AktivitetIkkeMulig( + medisinskArsak = null, + arbeidsrelatertArsak = ArbeidsrelatertArsak( + beskrivelse = "Datt i trappa", + arsak = listOf(ArbeidsrelatertArsakType.MANGLENDE_TILRETTELEGGING) + ) + ), + avventendeInnspillTilArbeidsgiver = null, + behandlingsdager = null, + gradert = null, + reisetilskudd = false + )) + } +} \ No newline at end of file diff --git a/src/test/kotlin/no/nav/sykdig/saf/SafJournalpostServiceTest.kt b/src/test/kotlin/no/nav/sykdig/saf/SafJournalpostServiceTest.kt index 8ef1ebb0..d5c2bd8f 100644 --- a/src/test/kotlin/no/nav/sykdig/saf/SafJournalpostServiceTest.kt +++ b/src/test/kotlin/no/nav/sykdig/saf/SafJournalpostServiceTest.kt @@ -2,6 +2,7 @@ package no.nav.sykdig.saf import io.mockk.every import io.mockk.mockk +import no.nav.sykdig.digitalisering.exceptions.MissingJournalpostException import no.nav.sykdig.digitalisering.saf.SafJournalpostGraphQlClient import no.nav.sykdig.digitalisering.saf.SafJournalpostService import no.nav.sykdig.digitalisering.saf.graphql.DokumentInfo @@ -9,11 +10,12 @@ import no.nav.sykdig.digitalisering.saf.graphql.Dokumentvariant import no.nav.sykdig.digitalisering.saf.graphql.Journalstatus import no.nav.sykdig.digitalisering.saf.graphql.SafJournalpost import no.nav.sykdig.digitalisering.saf.graphql.SafQueryJournalpost +import org.amshove.kluent.assertionError import org.amshove.kluent.internal.assertFailsWith -import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertNotNull -import org.junit.jupiter.api.Assertions.assertNull +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows class SafJournalpostServiceTest { private val safJournalpostGraphQlClient: SafJournalpostGraphQlClient = mockk() @@ -117,4 +119,65 @@ class SafJournalpostServiceTest { } assertEquals("Journalpost mangler PDF, $sykmeldingId", exception.message) } + + @Test + fun `er ikke journalført fordi status er mottatt`() { + val journalpostId = "123" + + every { safJournalpostGraphQlClient.getJournalpostM2m(journalpostId) } returns + SafQueryJournalpost( + SafJournalpost( + journalstatus = Journalstatus.MOTTATT, + dokumenter = + listOf( + DokumentInfo( + dokumentInfoId = "dok1", + tittel = "Dokument 1", + dokumentvarianter = listOf(Dokumentvariant(variantformat = "NON-ARKIV")), + brevkode = "1", + ), + ), + kanal = "EESSI", + avsenderMottaker = null, + bruker = null, + tema = null, + tittel = "Journalpost 1" + ), + ) + + val erIkkeJournalfort = safJournalpostService.erIkkeJournalfort(journalpostId) + assertTrue(erIkkeJournalfort) + } + @Test + fun `er ikke journalført fordi safjournalpost er null`() { + val journalpostId = "123" + every { safJournalpostGraphQlClient.getJournalpostM2m(journalpostId) } returns SafQueryJournalpost(null) + assertThrows{safJournalpostService.erIkkeJournalfort(journalpostId)} + } + @Test + fun `er journalført fordi status er ukjent`() { + val journalpostId = "123" + every { safJournalpostGraphQlClient.getJournalpostM2m(journalpostId) } returns + SafQueryJournalpost( + SafJournalpost( + journalstatus = Journalstatus.UKJENT, + dokumenter = + listOf( + DokumentInfo( + dokumentInfoId = "dok1", + tittel = "Dokument 1", + dokumentvarianter = listOf(Dokumentvariant(variantformat = "NON-ARKIV")), + brevkode = "1", + ), + ), + kanal = "EESSI", + avsenderMottaker = null, + bruker = null, + tema = null, + tittel = "Journalpost 2" + ), + ) + val erIkkeJournalfort = safJournalpostService.erIkkeJournalfort(journalpostId) + assertFalse(erIkkeJournalfort) + } } diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index 7bfcde08..c2434b99 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -16,7 +16,11 @@ spring: maximum-pool-size: 3 main: allow-bean-definition-overriding: true - + jpa: + properties: + hibernate: + show_sql: true + format_sql: true KAFKA_TRUSTSTORE_PATH: "" KAFKA_CREDSTORE_PASSWORD: "" KAFKA_SECURITY_PROTOCOL: "PLAINTEXT" @@ -40,6 +44,11 @@ dokarkiv.url: http://dokarkiv oppgave.url: http://oppgave smregistrering.url: http://smregistrering helsenett.url: http://syfohelsenettproxy +smtss.url: http://smtss +regel.url: http://regel + +mock-oauth2-server: + port: 8081 no.nav.security.jwt: issuer: @@ -97,11 +106,17 @@ no.nav.security.jwt: client-secret: secretzz client-auth-method: client_secret_basic + AZURE_APP_PRE_AUTHORIZED_APPS: "[{\"name\":\"dev-gcp:teamsykmelding:syk-dig\",\"clientId\":\"syk-dig-client-id\"}]" logging.config: "classpath:logback-test.xml" elector.path: dont_look_for_leader +logging: + level: + org.hibernate.SQL: DEBUG + org.hibernate.type.descriptor.sql.BasicBinder: TRACE + dgs: graphql: path: /api/graphql \ No newline at end of file