diff --git a/felles/src/main/kotlin/no/nav/helsearbeidsgiver/felles/EventTypes.kt b/felles/src/main/kotlin/no/nav/helsearbeidsgiver/felles/EventTypes.kt index 5c09afb21..b0a5e03e2 100644 --- a/felles/src/main/kotlin/no/nav/helsearbeidsgiver/felles/EventTypes.kt +++ b/felles/src/main/kotlin/no/nav/helsearbeidsgiver/felles/EventTypes.kt @@ -81,4 +81,5 @@ enum class EventName { OPPGAVE_OPPRETTET, OPPGAVE_LAGRET, OPPGAVE_FERDIGSTILT, + SAK_OG_OPPGAVE_UTGAATT, } diff --git a/notifikasjon/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/App.kt b/notifikasjon/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/App.kt index 5d4dfe73a..588656ba1 100644 --- a/notifikasjon/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/App.kt +++ b/notifikasjon/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/App.kt @@ -18,6 +18,7 @@ import no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.river.OpprettSakLoe import no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.river.OpprettSelvbestemtSakRiver import no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.river.SakFerdigLoeser import no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.river.SlettSakLoeser +import no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.river.UtgaattLoeser import no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.service.ManuellOpprettSakService import no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.service.OpprettOppgaveService import no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.service.OpprettSakService @@ -98,6 +99,9 @@ fun RapidsConnection.createNotifikasjonRivers( logger.info("Starter ${OppgaveFerdigLoeser::class.simpleName}...") OppgaveFerdigLoeser(this, arbeidsgiverNotifikasjonKlient, linkUrl) + logger.info("Starter ${UtgaattLoeser::class.simpleName}...") + UtgaattLoeser(this, arbeidsgiverNotifikasjonKlient, linkUrl) + logger.info("Starter ${SlettSakLoeser::class.simpleName}...") SlettSakLoeser(this, arbeidsgiverNotifikasjonKlient) diff --git a/notifikasjon/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/ArbeidsgiverNotifikasjonKlientUtils.kt b/notifikasjon/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/ArbeidsgiverNotifikasjonKlientUtils.kt index 4a700977f..001ea5f5b 100644 --- a/notifikasjon/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/ArbeidsgiverNotifikasjonKlientUtils.kt +++ b/notifikasjon/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/ArbeidsgiverNotifikasjonKlientUtils.kt @@ -18,6 +18,7 @@ object NotifikasjonTekst { const val OPPGAVE_TEKST = "Innsending av inntektsmelding" const val STATUS_TEKST_UNDER_BEHANDLING = "NAV trenger inntektsmelding" const val STATUS_TEKST_FERDIG = "Mottatt – Se kvittering eller korriger inntektsmelding" + const val STATUS_TEKST_AVBRUTT = "Avbrutt av NAV" fun lenkeFerdigstilt( linkUrl: String, @@ -95,3 +96,27 @@ fun ArbeidsgiverNotifikasjonKlient.ferdigstillSak( ) } } + +fun ArbeidsgiverNotifikasjonKlient.avbrytSak( + forespoerselId: UUID, + nyLenke: String, +): Result = + Metrics.agNotifikasjonRequest.recordTime(::nyStatusSakByGrupperingsid) { + runCatching { + nyStatusSakByGrupperingsid( + grupperingsid = forespoerselId.toString(), + merkelapp = NotifikasjonTekst.MERKELAPP, + status = SaksStatus.FERDIG, + statusTekst = NotifikasjonTekst.STATUS_TEKST_AVBRUTT, + nyLenke = nyLenke, + ) + }.recoverCatching { + nyStatusSakByGrupperingsid( + grupperingsid = forespoerselId.toString(), + merkelapp = NotifikasjonTekst.MERKELAPP_GAMMEL, + status = SaksStatus.FERDIG, + statusTekst = NotifikasjonTekst.STATUS_TEKST_AVBRUTT, + nyLenke = nyLenke, + ) + } + } diff --git a/notifikasjon/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/river/UtgaattLoeser.kt b/notifikasjon/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/river/UtgaattLoeser.kt new file mode 100644 index 000000000..3d68f6057 --- /dev/null +++ b/notifikasjon/src/main/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/river/UtgaattLoeser.kt @@ -0,0 +1,141 @@ +package no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.river + +import com.github.navikt.tbd_libs.rapids_and_rivers.JsonMessage +import com.github.navikt.tbd_libs.rapids_and_rivers.River +import com.github.navikt.tbd_libs.rapids_and_rivers_api.MessageContext +import com.github.navikt.tbd_libs.rapids_and_rivers_api.RapidsConnection +import kotlinx.serialization.json.JsonElement +import no.nav.helsearbeidsgiver.arbeidsgivernotifikasjon.ArbeidsgiverNotifikasjonKlient +import no.nav.helsearbeidsgiver.arbeidsgivernotifikasjon.SakEllerOppgaveFinnesIkkeException +import no.nav.helsearbeidsgiver.felles.EventName +import no.nav.helsearbeidsgiver.felles.Key +import no.nav.helsearbeidsgiver.felles.json.les +import no.nav.helsearbeidsgiver.felles.json.toJson +import no.nav.helsearbeidsgiver.felles.json.toMap +import no.nav.helsearbeidsgiver.felles.metrics.Metrics +import no.nav.helsearbeidsgiver.felles.rapidsrivers.demandValues +import no.nav.helsearbeidsgiver.felles.rapidsrivers.publish +import no.nav.helsearbeidsgiver.felles.rapidsrivers.requireKeys +import no.nav.helsearbeidsgiver.felles.utils.Log +import no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.NotifikasjonTekst.MERKELAPP +import no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.NotifikasjonTekst.MERKELAPP_GAMMEL +import no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.avbrytSak +import no.nav.helsearbeidsgiver.utils.json.parseJson +import no.nav.helsearbeidsgiver.utils.json.serializer.UuidSerializer +import no.nav.helsearbeidsgiver.utils.json.toJson +import no.nav.helsearbeidsgiver.utils.json.toPretty +import no.nav.helsearbeidsgiver.utils.log.MdcUtils +import no.nav.helsearbeidsgiver.utils.log.logger +import no.nav.helsearbeidsgiver.utils.log.sikkerLogger +import java.util.UUID + +class UtgaattLoeser( + rapid: RapidsConnection, + private val agNotifikasjonKlient: ArbeidsgiverNotifikasjonKlient, + private val linkUrl: String, +) : River.PacketListener { + private val logger = logger() + private val sikkerLogger = sikkerLogger() + + init { + River(rapid) + .apply { + validate { + it.demandValues( + Key.EVENT_NAME to EventName.FORESPOERSEL_FORKASTET.name, + ) + it.requireKeys( + Key.UUID, + Key.FORESPOERSEL_ID, + ) + } + }.register(this) + } + + override fun onPacket( + packet: JsonMessage, + context: MessageContext, + ) { + if (packet[Key.FORESPOERSEL_ID.str].asText().isEmpty()) { + logger.warn("Mangler forespørselId!") + sikkerLogger.warn("Mangler forespørselId!") + } + val json = packet.toJson().parseJson() + + logger.info("Mottok melding med event '${EventName.FORESPOERSEL_FORKASTET}'.") + sikkerLogger.info("Mottok melding:\n${json.toPretty()}") + + MdcUtils.withLogFields( + Log.klasse(this), + Log.event(EventName.FORESPOERSEL_FORKASTET), + ) { + runCatching { + haandterMelding(json.toMap(), context) + }.onFailure { e -> + "Ukjent feil.".also { + logger.error("$it Se sikker logg for mer info.") + sikkerLogger.error(it, e) + } + } + } + } + + private fun haandterMelding( + melding: Map, + context: MessageContext, + ) { + val forespoerselId = Key.FORESPOERSEL_ID.les(UuidSerializer, melding) + val transaksjonId = Key.UUID.les(UuidSerializer, melding) + + MdcUtils.withLogFields( + Log.forespoerselId(forespoerselId), + Log.transaksjonId(transaksjonId), + ) { + settUtgaatt(forespoerselId, transaksjonId, context) + } + } + + private fun settUtgaatt( + forespoerselId: UUID, + transaksjonId: UUID, + context: MessageContext, + ) { + Metrics.agNotifikasjonRequest.recordTime(agNotifikasjonKlient::oppgaveUtgaattByEksternId) { + runCatching { + agNotifikasjonKlient.oppgaveUtgaattByEksternId( + merkelapp = MERKELAPP, + eksternId = forespoerselId.toString(), + nyLenke = "$linkUrl/im-dialog/utgatt", + ) + }.recoverCatching { + agNotifikasjonKlient.oppgaveUtgaattByEksternId( + merkelapp = MERKELAPP_GAMMEL, + eksternId = forespoerselId.toString(), + nyLenke = "$linkUrl/im-dialog/utgatt", + ) + }.onFailure { + if (it is SakEllerOppgaveFinnesIkkeException) { + logger.warn(it.message) + sikkerLogger.warn(it.message) + } else { + throw it + } + } + } + + agNotifikasjonKlient.avbrytSak(forespoerselId, "$linkUrl/im-dialog/utgatt").onFailure { + if (it is SakEllerOppgaveFinnesIkkeException) { + logger.warn(it.message) + sikkerLogger.warn(it.message) + } else { + throw it + } + } + + context.publish( + Key.EVENT_NAME to EventName.SAK_OG_OPPGAVE_UTGAATT.toJson(), + Key.FORESPOERSEL_ID to forespoerselId.toJson(), + Key.UUID to transaksjonId.toJson(), + ) + } +} diff --git a/notifikasjon/src/test/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/river/UtgaattLoeserTest.kt b/notifikasjon/src/test/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/river/UtgaattLoeserTest.kt new file mode 100644 index 000000000..76aa4c7fe --- /dev/null +++ b/notifikasjon/src/test/kotlin/no/nav/helsearbeidsgiver/inntektsmelding/notifikasjon/river/UtgaattLoeserTest.kt @@ -0,0 +1,261 @@ +package no.nav.helsearbeidsgiver.inntektsmelding.notifikasjon.river + +import com.github.navikt.tbd_libs.rapids_and_rivers.test_support.TestRapid +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.ints.shouldBeExactly +import io.kotest.matchers.shouldBe +import io.mockk.clearAllMocks +import io.mockk.coEvery +import io.mockk.coVerifySequence +import io.mockk.mockk +import no.nav.helsearbeidsgiver.arbeidsgivernotifikasjon.ArbeidsgiverNotifikasjonKlient +import no.nav.helsearbeidsgiver.arbeidsgivernotifikasjon.SakEllerOppgaveFinnesIkkeException +import no.nav.helsearbeidsgiver.arbeidsgivernotifkasjon.graphql.generated.enums.SaksStatus +import no.nav.helsearbeidsgiver.felles.EventName +import no.nav.helsearbeidsgiver.felles.Key +import no.nav.helsearbeidsgiver.felles.json.toJson +import no.nav.helsearbeidsgiver.felles.json.toMap +import no.nav.helsearbeidsgiver.felles.test.rapidsrivers.firstMessage +import no.nav.helsearbeidsgiver.felles.test.rapidsrivers.sendJson +import no.nav.helsearbeidsgiver.utils.json.toJson +import java.util.UUID + +class UtgaattLoeserTest : + FunSpec({ + val testRapid = TestRapid() + val mockAgNotifikasjonKlient = mockk(relaxed = true) + + UtgaattLoeser(testRapid, mockAgNotifikasjonKlient, Mock.linkUrl) + + beforeEach { + testRapid.reset() + clearAllMocks() + } + + test("Ved forkastet forespørsel med forespørsel-ID settes oppgaven til utgått og sak til ferdig") { + val expected = + mapOf( + Key.EVENT_NAME to EventName.SAK_OG_OPPGAVE_UTGAATT.toJson(), + Key.UUID to Mock.transaksjonId.toJson(), + Key.FORESPOERSEL_ID to Mock.forespoerselId.toJson(), + ) + + testRapid.sendJson( + Key.EVENT_NAME to EventName.FORESPOERSEL_FORKASTET.toJson(), + Key.FORESPOERSEL_ID to Mock.forespoerselId.toJson(), + Key.UUID to Mock.transaksjonId.toJson(), + ) + + testRapid.inspektør.size shouldBeExactly 1 + + val actual = testRapid.firstMessage().toMap() + + actual shouldBe expected + + coVerifySequence { + mockAgNotifikasjonKlient.oppgaveUtgaattByEksternId( + merkelapp = "Inntektsmelding sykepenger", + eksternId = Mock.forespoerselId.toString(), + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + mockAgNotifikasjonKlient.nyStatusSakByGrupperingsid( + grupperingsid = Mock.forespoerselId.toString(), + merkelapp = "Inntektsmelding sykepenger", + status = SaksStatus.FERDIG, + tidspunkt = null, + statusTekst = "Avbrutt av NAV", + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + } + } + test("Hvis oppdatering av oppgave og sak feiler med SakEllerOppgaveFinnesIkkeException skal oppgaven og saken oppdateres med gammel merkelapp") { + coEvery { + mockAgNotifikasjonKlient.oppgaveUtgaattByEksternId("Inntektsmelding sykepenger", any(), any()) + } throws SakEllerOppgaveFinnesIkkeException("Feil fra agNotikitasjonKlient") + coEvery { + mockAgNotifikasjonKlient.nyStatusSakByGrupperingsid(any(), "Inntektsmelding sykepenger", any(), any(), any(), any()) + } throws SakEllerOppgaveFinnesIkkeException("Feil fra agNotikitasjonKlient") + + val expected = + mapOf( + Key.EVENT_NAME to EventName.SAK_OG_OPPGAVE_UTGAATT.toJson(), + Key.UUID to Mock.transaksjonId.toJson(), + Key.FORESPOERSEL_ID to Mock.forespoerselId.toJson(), + ) + + testRapid.sendJson( + Key.EVENT_NAME to EventName.FORESPOERSEL_FORKASTET.toJson(), + Key.FORESPOERSEL_ID to Mock.forespoerselId.toJson(), + Key.UUID to Mock.transaksjonId.toJson(), + ) + + testRapid.inspektør.size shouldBeExactly 1 + + val actual = testRapid.firstMessage().toMap() + + actual shouldBe expected + + coVerifySequence { + // Feiler + mockAgNotifikasjonKlient.oppgaveUtgaattByEksternId( + merkelapp = "Inntektsmelding sykepenger", + eksternId = Mock.forespoerselId.toString(), + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + // Feiler ikke + mockAgNotifikasjonKlient.oppgaveUtgaattByEksternId( + merkelapp = "Inntektsmelding", + eksternId = Mock.forespoerselId.toString(), + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + // Feiler + mockAgNotifikasjonKlient.nyStatusSakByGrupperingsid( + grupperingsid = Mock.forespoerselId.toString(), + merkelapp = "Inntektsmelding sykepenger", + status = SaksStatus.FERDIG, + statusTekst = "Avbrutt av NAV", + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + // Feiler ikke + mockAgNotifikasjonKlient.nyStatusSakByGrupperingsid( + grupperingsid = Mock.forespoerselId.toString(), + merkelapp = "Inntektsmelding", + status = SaksStatus.FERDIG, + statusTekst = "Avbrutt av NAV", + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + } + } + + test("Hvis oppgaveUtgaattByEksternId med ny og gammel merkelapp feiler med SakEllerOppgaveFinnesIkkeException skal saken avbrytes likevel") { + coEvery { + mockAgNotifikasjonKlient.oppgaveUtgaattByEksternId(any(), any(), any()) + } throws SakEllerOppgaveFinnesIkkeException("Feil fra agNotikitasjonKlient") + + val expected = + mapOf( + Key.EVENT_NAME to EventName.SAK_OG_OPPGAVE_UTGAATT.toJson(), + Key.UUID to Mock.transaksjonId.toJson(), + Key.FORESPOERSEL_ID to Mock.forespoerselId.toJson(), + ) + + testRapid.sendJson( + Key.EVENT_NAME to EventName.FORESPOERSEL_FORKASTET.toJson(), + Key.FORESPOERSEL_ID to Mock.forespoerselId.toJson(), + Key.UUID to Mock.transaksjonId.toJson(), + ) + + testRapid.inspektør.size shouldBeExactly 1 + + val actual = testRapid.firstMessage().toMap() + + actual shouldBe expected + + coVerifySequence { + // Feiler + mockAgNotifikasjonKlient.oppgaveUtgaattByEksternId( + merkelapp = "Inntektsmelding sykepenger", + eksternId = Mock.forespoerselId.toString(), + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + // Feiler + mockAgNotifikasjonKlient.oppgaveUtgaattByEksternId( + merkelapp = "Inntektsmelding", + eksternId = Mock.forespoerselId.toString(), + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + // Feiler ikke + mockAgNotifikasjonKlient.nyStatusSakByGrupperingsid( + grupperingsid = Mock.forespoerselId.toString(), + merkelapp = "Inntektsmelding sykepenger", + status = SaksStatus.FERDIG, + statusTekst = "Avbrutt av NAV", + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + } + } + + test("Hvis avbrytSak med ny og gammel merkelapp feiler med SakEllerOppgaveFinnesIkkeException skal oppgaven settes til utgått likevel") { + coEvery { + mockAgNotifikasjonKlient.nyStatusSakByGrupperingsid(any(), any(), any(), any(), any(), any()) + } throws SakEllerOppgaveFinnesIkkeException("Feil fra agNotikitasjonKlient") + + val expected = + mapOf( + Key.EVENT_NAME to EventName.SAK_OG_OPPGAVE_UTGAATT.toJson(), + Key.UUID to Mock.transaksjonId.toJson(), + Key.FORESPOERSEL_ID to Mock.forespoerselId.toJson(), + ) + + testRapid.sendJson( + Key.EVENT_NAME to EventName.FORESPOERSEL_FORKASTET.toJson(), + Key.FORESPOERSEL_ID to Mock.forespoerselId.toJson(), + Key.UUID to Mock.transaksjonId.toJson(), + ) + + testRapid.inspektør.size shouldBeExactly 1 + + val actual = testRapid.firstMessage().toMap() + + actual shouldBe expected + + coVerifySequence { + // Feiler ikke + mockAgNotifikasjonKlient.oppgaveUtgaattByEksternId( + merkelapp = "Inntektsmelding sykepenger", + eksternId = Mock.forespoerselId.toString(), + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + // Feiler + mockAgNotifikasjonKlient.nyStatusSakByGrupperingsid( + grupperingsid = Mock.forespoerselId.toString(), + merkelapp = "Inntektsmelding sykepenger", + status = SaksStatus.FERDIG, + statusTekst = "Avbrutt av NAV", + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + // Feiler + mockAgNotifikasjonKlient.nyStatusSakByGrupperingsid( + grupperingsid = Mock.forespoerselId.toString(), + merkelapp = "Inntektsmelding", + status = SaksStatus.FERDIG, + statusTekst = "Avbrutt av NAV", + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + } + } + test("Ved feil ved oppgaveUtgaattByEksternId med ny og gammel merkelapp skal saken ikke avbrytes") { + coEvery { + mockAgNotifikasjonKlient.oppgaveUtgaattByEksternId(any(), any(), any()) + } throws Exception("Feil fra agNotikitasjonKlient") + + testRapid.sendJson( + Key.EVENT_NAME to EventName.FORESPOERSEL_FORKASTET.toJson(), + Key.FORESPOERSEL_ID to Mock.forespoerselId.toJson(), + Key.UUID to Mock.transaksjonId.toJson(), + ) + + testRapid.inspektør.size shouldBeExactly 0 + + coVerifySequence { + // Feiler + mockAgNotifikasjonKlient.oppgaveUtgaattByEksternId( + merkelapp = "Inntektsmelding sykepenger", + eksternId = Mock.forespoerselId.toString(), + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + // Feiler + mockAgNotifikasjonKlient.oppgaveUtgaattByEksternId( + merkelapp = "Inntektsmelding", + eksternId = Mock.forespoerselId.toString(), + nyLenke = "${Mock.linkUrl}/im-dialog/utgatt", + ) + } + } + }) + +private object Mock { + val linkUrl = "enSlagsUrl" + val forespoerselId = UUID.randomUUID() + val transaksjonId = UUID.randomUUID() +}