diff --git a/client/src/main/kotlin/no/nav/su/se/bakover/client/oppgave/OppgaveHttpClient.kt b/client/src/main/kotlin/no/nav/su/se/bakover/client/oppgave/OppgaveHttpClient.kt index fca38b8aad..44d75293b3 100644 --- a/client/src/main/kotlin/no/nav/su/se/bakover/client/oppgave/OppgaveHttpClient.kt +++ b/client/src/main/kotlin/no/nav/su/se/bakover/client/oppgave/OppgaveHttpClient.kt @@ -113,6 +113,10 @@ internal class OppgaveHttpClient( "--- ${ Tidspunkt.now(clock).toOppgaveFormat() } - Opprettet av Supplerende Stønad ---\nSaksnummer : ${config.saksreferanse}" + is OppgaveConfig.Klage.Vedtak -> + "--- ${ + Tidspunkt.now(clock).toOppgaveFormat() + } - Opprettet av Supplerende Stønad ---\nSaksnummer : ${config.saksreferanse}\n${OppgavebeskrivelseMapper.map(config.utfall)}" is OppgaveConfig.Klage -> "--- ${ Tidspunkt.now(clock).toOppgaveFormat() diff --git a/client/src/main/kotlin/no/nav/su/se/bakover/client/oppgave/OppgavebeskrivelseMapper.kt b/client/src/main/kotlin/no/nav/su/se/bakover/client/oppgave/OppgavebeskrivelseMapper.kt index 6d5276a271..86481b3d50 100644 --- a/client/src/main/kotlin/no/nav/su/se/bakover/client/oppgave/OppgavebeskrivelseMapper.kt +++ b/client/src/main/kotlin/no/nav/su/se/bakover/client/oppgave/OppgavebeskrivelseMapper.kt @@ -1,9 +1,14 @@ package no.nav.su.se.bakover.client.oppgave import no.nav.su.se.bakover.domain.hendelse.Personhendelse +import no.nav.su.se.bakover.domain.klage.KlagevedtakUtfall import no.nav.su.se.bakover.domain.person.SivilstandTyper object OppgavebeskrivelseMapper { + fun map(utfall: KlagevedtakUtfall) { + "Utfall: ${utfall.toReadableName()}\n\n${utfall.LukkBeskrivelse()}" + } + fun map(hendelse: Personhendelse.Hendelse) = when (hendelse) { is Personhendelse.Hendelse.Dødsfall -> { "Dødsfall\n" + @@ -33,4 +38,31 @@ object OppgavebeskrivelseMapper { SivilstandTyper.SKILT_PARTNER -> "Skilt partner" SivilstandTyper.GJENLEVENDE_PARTNER -> "Gjenlevende partner" } + + private fun KlagevedtakUtfall.toReadableName() = when (this) { + KlagevedtakUtfall.TRUKKET -> "Trukket" + KlagevedtakUtfall.RETUR -> "Retur" + KlagevedtakUtfall.OPPHEVET -> "Opphevet" + KlagevedtakUtfall.MEDHOLD -> "Medhold" + KlagevedtakUtfall.DELVIS_MEDHOLD -> "Delvis medhold" + KlagevedtakUtfall.STADFESTELSE -> "Stadfestelse" + KlagevedtakUtfall.UGUNST -> "Ugunst" + KlagevedtakUtfall.AVVIST -> "Avvist" + } + private fun KlagevedtakUtfall.LukkBeskrivelse() = when (this) { + /* + * Informasjonsoppgaver som må lukkes manuelt. + * */ + KlagevedtakUtfall.TRUKKET, + KlagevedtakUtfall.STADFESTELSE, + KlagevedtakUtfall.AVVIST -> "Denne oppgaven er kun til opplysning og må lukkes manuelt." + /* + * Oppgaver som krever handling. Lukkes automatisk av `su-se-bakover`. + * */ + KlagevedtakUtfall.RETUR, + KlagevedtakUtfall.OPPHEVET, + KlagevedtakUtfall.MEDHOLD, + KlagevedtakUtfall.DELVIS_MEDHOLD, + KlagevedtakUtfall.UGUNST -> "Klagen krever ytterligere saksbehandling. Lukking av oppgaven håndteres automatisk." + } } diff --git a/database/src/main/kotlin/no/nav/su/se/bakover/database/klage/KlagevedtakPostgresRepo.kt b/database/src/main/kotlin/no/nav/su/se/bakover/database/klage/KlagevedtakPostgresRepo.kt index 0d7a9630f5..15a718b997 100644 --- a/database/src/main/kotlin/no/nav/su/se/bakover/database/klage/KlagevedtakPostgresRepo.kt +++ b/database/src/main/kotlin/no/nav/su/se/bakover/database/klage/KlagevedtakPostgresRepo.kt @@ -2,16 +2,28 @@ package no.nav.su.se.bakover.database.klage import arrow.core.Either import kotliquery.Row +import no.nav.su.se.bakover.common.persistence.TransactionContext import no.nav.su.se.bakover.database.PostgresSessionFactory +import no.nav.su.se.bakover.database.PostgresTransactionContext.Companion.withTransaction import no.nav.su.se.bakover.database.hentListe import no.nav.su.se.bakover.database.insert +import no.nav.su.se.bakover.database.oppdatering import no.nav.su.se.bakover.database.tidspunkt import no.nav.su.se.bakover.database.uuid import no.nav.su.se.bakover.domain.klage.KlagevedtakRepo +import no.nav.su.se.bakover.domain.klage.ProsessertKlagevedtak import no.nav.su.se.bakover.domain.klage.UprosessertFattetKlagevedtak import org.postgresql.util.PSQLException +import java.util.UUID internal class KlagevedtakPostgresRepo(private val sessionFactory: PostgresSessionFactory) : KlagevedtakRepo { + internal enum class KlagevedtakType(private val verdi: String) { + UPROSESSERT("UPROSESSERT"), + PROSESSERT("PROSESSERT"), + FEIL("FEIL"); + + override fun toString(): String = verdi + } override fun lagre(klagevedtak: UprosessertFattetKlagevedtak) { Either.catch { @@ -24,7 +36,7 @@ internal class KlagevedtakPostgresRepo(private val sessionFactory: PostgresSessi params = mapOf( "id" to klagevedtak.id, "opprettet" to klagevedtak.opprettet, - "type" to "UPROSESSERT", + "type" to KlagevedtakType.UPROSESSERT.toString(), "metadata" to klagevedtak.metadata.toDatabaseJson(), ), session = session, @@ -40,10 +52,28 @@ internal class KlagevedtakPostgresRepo(private val sessionFactory: PostgresSessi } } + override fun lagre(klagevedtak: ProsessertKlagevedtak, transactionContext: TransactionContext) { + transactionContext.withTransaction { transaction -> + """ + update klagevedtak + set type = :type, oppgaveId = :oppgaveid + where id = :id + """.trimIndent() + .insert( + params = mapOf( + "id" to klagevedtak.id, + "type" to KlagevedtakType.PROSESSERT.toString(), + "oppgaveid" to klagevedtak.oppgaveId, + ), + session = transaction, + ) + } + } + override fun hentUbehandlaKlagevedtak(): List { return sessionFactory.withSession { session -> """ - select * from klagevedtak where type = 'UPROSESSERT' + select * from klagevedtak where type = '${KlagevedtakType.UPROSESSERT}' """.trimIndent().hentListe( emptyMap(), session, @@ -51,6 +81,14 @@ internal class KlagevedtakPostgresRepo(private val sessionFactory: PostgresSessi } } + override fun markerSomFeil(id: UUID) { + sessionFactory.withSession { session -> + """ + update klagevedtak set type = '${KlagevedtakType.FEIL}' where id = :id + """.trimIndent().oppdatering(mapOf("id" to id), session) + } + } + private fun rowToKlage(row: Row): UprosessertFattetKlagevedtak { return UprosessertFattetKlagevedtak( id = row.uuid("id"), @@ -58,4 +96,8 @@ internal class KlagevedtakPostgresRepo(private val sessionFactory: PostgresSessi metadata = KlagevedtakMetadataJson.toKlagevedtakMetadata(row.string("metadata")), ) } + + override fun defaultTransactionContext(): TransactionContext { + return sessionFactory.newTransactionContext() + } } diff --git a/database/src/main/resources/db/migration/V117__oppgaveid_klagevedtak.sql b/database/src/main/resources/db/migration/V117__oppgaveid_klagevedtak.sql new file mode 100644 index 0000000000..17c981ac08 --- /dev/null +++ b/database/src/main/resources/db/migration/V117__oppgaveid_klagevedtak.sql @@ -0,0 +1 @@ +alter table klagevedtak add column oppgaveid text \ No newline at end of file diff --git a/database/src/test/kotlin/no/nav/su/se/bakover/database/klage/KlagevedtakPostgresRepoTest.kt b/database/src/test/kotlin/no/nav/su/se/bakover/database/klage/KlagevedtakPostgresRepoTest.kt index 58f48b2b3c..fbaf2de074 100644 --- a/database/src/test/kotlin/no/nav/su/se/bakover/database/klage/KlagevedtakPostgresRepoTest.kt +++ b/database/src/test/kotlin/no/nav/su/se/bakover/database/klage/KlagevedtakPostgresRepoTest.kt @@ -5,6 +5,8 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import no.nav.su.se.bakover.database.TestDataHelper import no.nav.su.se.bakover.database.withMigratedDb +import no.nav.su.se.bakover.domain.klage.KlagevedtakUtfall +import no.nav.su.se.bakover.domain.klage.ProsessertKlagevedtak import no.nav.su.se.bakover.domain.klage.UprosessertFattetKlagevedtak import no.nav.su.se.bakover.test.fixedTidspunkt import org.junit.jupiter.api.Test @@ -81,4 +83,37 @@ internal class KlagevedtakPostgresRepoTest { } } } + + @Test + fun `Endrer og lagrer type til PROSESSERT`() { + withMigratedDb { dataSource -> + val testDataHelper = TestDataHelper(dataSource) + val klagevedtakRepo = testDataHelper.klagevedtakPostgresRepo + val id = UUID.randomUUID() + UprosessertFattetKlagevedtak( + id = id, + opprettet = fixedTidspunkt, + metadata = UprosessertFattetKlagevedtak.Metadata( + hendelseId = UUID.randomUUID().toString(), + offset = 1, + partisjon = 2, + key = UUID.randomUUID().toString(), + value = "{}", + ), + ).also { + klagevedtakRepo.lagre(it) + klagevedtakRepo.lagre( + ProsessertKlagevedtak( + id = it.id, + eventId = it.metadata.hendelseId, + klageId = UUID.randomUUID(), + utfall = KlagevedtakUtfall.STADFESTELSE, + vedtaksbrevReferanse = UUID.randomUUID().toString(), + oppgaveId = null + ) + ) + klagevedtakRepo.hentUbehandlaKlagevedtak() shouldBe emptyList() + } + } + } } diff --git a/domain/src/main/kotlin/no/nav/su/se/bakover/domain/klage/Klagevedtak.kt b/domain/src/main/kotlin/no/nav/su/se/bakover/domain/klage/Klagevedtak.kt new file mode 100644 index 0000000000..eb823d7117 --- /dev/null +++ b/domain/src/main/kotlin/no/nav/su/se/bakover/domain/klage/Klagevedtak.kt @@ -0,0 +1,49 @@ +package no.nav.su.se.bakover.domain.klage + +import no.nav.su.se.bakover.domain.oppgave.OppgaveId +import java.util.UUID + +/** + * Representerer ett fattet klagevedtak av Kabal. + */ +data class UprosessertKlagevedtak( + val id: UUID, + val eventId: String, + val klageId: UUID, + val utfall: KlagevedtakUtfall, + val vedtaksbrevReferanse: String, +) { + fun tilProsessert(oppgaveId: OppgaveId?) = ProsessertKlagevedtak( + id = id, + eventId = eventId, + klageId = klageId, + utfall = utfall, + vedtaksbrevReferanse = vedtaksbrevReferanse, + oppgaveId = oppgaveId, + ) +} + +data class ProsessertKlagevedtak( + val id: UUID, + val eventId: String, + val klageId: UUID, + val utfall: KlagevedtakUtfall, + val vedtaksbrevReferanse: String, + val oppgaveId: OppgaveId?, +) + +enum class KlagevedtakUtfall { + TRUKKET, + RETUR, + OPPHEVET, + MEDHOLD, + DELVIS_MEDHOLD, + STADFESTELSE, + UGUNST, + AVVIST +} + +sealed interface KanIkkeTolkeKlagevedtak { + object KunneIkkeDeserialisere : KanIkkeTolkeKlagevedtak + object UgyldigeVerdier : KanIkkeTolkeKlagevedtak +} diff --git a/domain/src/main/kotlin/no/nav/su/se/bakover/domain/klage/KlagevedtakRepo.kt b/domain/src/main/kotlin/no/nav/su/se/bakover/domain/klage/KlagevedtakRepo.kt index 1093a7150c..041fd81d45 100644 --- a/domain/src/main/kotlin/no/nav/su/se/bakover/domain/klage/KlagevedtakRepo.kt +++ b/domain/src/main/kotlin/no/nav/su/se/bakover/domain/klage/KlagevedtakRepo.kt @@ -1,6 +1,12 @@ package no.nav.su.se.bakover.domain.klage +import no.nav.su.se.bakover.common.persistence.TransactionContext +import java.util.UUID + interface KlagevedtakRepo { fun lagre(klagevedtak: UprosessertFattetKlagevedtak) + fun lagre(klagevedtak: ProsessertKlagevedtak, transactionContext: TransactionContext = defaultTransactionContext()) fun hentUbehandlaKlagevedtak(): List + fun markerSomFeil(id: UUID) + fun defaultTransactionContext(): TransactionContext } diff --git a/domain/src/main/kotlin/no/nav/su/se/bakover/domain/oppgave/OppgaveConfig.kt b/domain/src/main/kotlin/no/nav/su/se/bakover/domain/oppgave/OppgaveConfig.kt index 962653b4b5..1d94911acb 100644 --- a/domain/src/main/kotlin/no/nav/su/se/bakover/domain/oppgave/OppgaveConfig.kt +++ b/domain/src/main/kotlin/no/nav/su/se/bakover/domain/oppgave/OppgaveConfig.kt @@ -7,6 +7,7 @@ import no.nav.su.se.bakover.domain.NavIdentBruker import no.nav.su.se.bakover.domain.Oppgavetype import no.nav.su.se.bakover.domain.Saksnummer import no.nav.su.se.bakover.domain.journal.JournalpostId +import no.nav.su.se.bakover.domain.klage.KlagevedtakUtfall import java.time.Clock import java.time.LocalDate import java.util.UUID @@ -125,6 +126,37 @@ sealed class OppgaveConfig { override val aktivDato: LocalDate by lazy { LocalDate.now(clock) } override val fristFerdigstillelse: LocalDate by lazy { aktivDato.plusDays(30) } + /** + * Opprettes av en job som prosesserer vedtaken fra Klageinstans. Består av: + * 1) Oppgaver som bara formidler informasjon, disse må lukkes av saksbehandler selv i gosys. + * 2) Oppgaver som krever ytterliggere saksbehandling på klagen. Disse lukker systemet selv. + * */ + sealed class Vedtak : Klage() { + abstract val utfall: KlagevedtakUtfall + + data class Handling( + override val saksnummer: Saksnummer, + override val aktørId: AktørId, + override val journalpostId: JournalpostId, + override val tilordnetRessurs: NavIdentBruker?, + override val clock: Clock, + override val utfall: KlagevedtakUtfall, + ) : Vedtak() { + override val oppgavetype = Oppgavetype.BEHANDLE_SAK + } + + data class Informasjon( + override val saksnummer: Saksnummer, + override val aktørId: AktørId, + override val journalpostId: JournalpostId, + override val tilordnetRessurs: NavIdentBruker?, + override val clock: Clock, + override val utfall: KlagevedtakUtfall + ) : Vedtak() { + override val oppgavetype = Oppgavetype.VURDER_KONSEKVENS_FOR_YTELSE + } + } + /** * Dette er saksbehandlingsoppgaven som opprettes: * 1) Når en klage opprettes diff --git a/service/src/main/kotlin/no/nav/su/se/bakover/service/AccessCheckProxy.kt b/service/src/main/kotlin/no/nav/su/se/bakover/service/AccessCheckProxy.kt index 434f745c7a..93901d3da4 100644 --- a/service/src/main/kotlin/no/nav/su/se/bakover/service/AccessCheckProxy.kt +++ b/service/src/main/kotlin/no/nav/su/se/bakover/service/AccessCheckProxy.kt @@ -23,6 +23,7 @@ import no.nav.su.se.bakover.domain.beregning.Beregning import no.nav.su.se.bakover.domain.brev.LagBrevRequest import no.nav.su.se.bakover.domain.dokument.Dokument import no.nav.su.se.bakover.domain.grunnlag.Grunnlag +import no.nav.su.se.bakover.domain.klage.KanIkkeTolkeKlagevedtak import no.nav.su.se.bakover.domain.klage.KlageTilAttestering import no.nav.su.se.bakover.domain.klage.KunneIkkeBekrefteKlagesteg import no.nav.su.se.bakover.domain.klage.KunneIkkeOppretteKlage @@ -34,6 +35,7 @@ import no.nav.su.se.bakover.domain.klage.KunneIkkeVurdereKlage import no.nav.su.se.bakover.domain.klage.OpprettetKlage import no.nav.su.se.bakover.domain.klage.OversendtKlage import no.nav.su.se.bakover.domain.klage.UprosessertFattetKlagevedtak +import no.nav.su.se.bakover.domain.klage.UprosessertKlagevedtak import no.nav.su.se.bakover.domain.klage.VilkårsvurdertKlage import no.nav.su.se.bakover.domain.klage.VurdertKlage import no.nav.su.se.bakover.domain.nøkkeltall.Nøkkeltall @@ -828,6 +830,9 @@ open class AccessCheckProxy( }, klagevedtakService = object : KlagevedtakService { override fun lagre(klageVedtak: UprosessertFattetKlagevedtak) = kastKanKunKallesFraAnnenService() + override fun håndterUtfallFraKlageinstans(deserializeAndMap: (id: UUID, json: String) -> Either) { + kastKanKunKallesFraAnnenService() + } }, ) } diff --git a/service/src/main/kotlin/no/nav/su/se/bakover/service/ServiceBuilder.kt b/service/src/main/kotlin/no/nav/su/se/bakover/service/ServiceBuilder.kt index 7c1a2f6b5e..472ddaff7b 100644 --- a/service/src/main/kotlin/no/nav/su/se/bakover/service/ServiceBuilder.kt +++ b/service/src/main/kotlin/no/nav/su/se/bakover/service/ServiceBuilder.kt @@ -158,6 +158,10 @@ object ServiceBuilder { ) val klagevedtakService = KlagevedtakServiceImpl( klagevedtakRepo = databaseRepos.klageVedtakRepo, + klageRepo = databaseRepos.klageRepo, + oppgaveService = oppgaveService, + personService = personService, + sessionFactory = databaseRepos.sessionFactory ) return Services( avstemming = AvstemmingServiceImpl( diff --git a/service/src/main/kotlin/no/nav/su/se/bakover/service/klage/KlagevedtakService.kt b/service/src/main/kotlin/no/nav/su/se/bakover/service/klage/KlagevedtakService.kt index b8601e9fa1..214f27b132 100644 --- a/service/src/main/kotlin/no/nav/su/se/bakover/service/klage/KlagevedtakService.kt +++ b/service/src/main/kotlin/no/nav/su/se/bakover/service/klage/KlagevedtakService.kt @@ -1,7 +1,12 @@ package no.nav.su.se.bakover.service.klage +import arrow.core.Either +import no.nav.su.se.bakover.domain.klage.KanIkkeTolkeKlagevedtak import no.nav.su.se.bakover.domain.klage.UprosessertFattetKlagevedtak +import no.nav.su.se.bakover.domain.klage.UprosessertKlagevedtak +import java.util.UUID interface KlagevedtakService { fun lagre(klageVedtak: UprosessertFattetKlagevedtak) + fun håndterUtfallFraKlageinstans(deserializeAndMap: (id: UUID, json: String) -> Either) } diff --git a/service/src/main/kotlin/no/nav/su/se/bakover/service/klage/KlagevedtakServiceImpl.kt b/service/src/main/kotlin/no/nav/su/se/bakover/service/klage/KlagevedtakServiceImpl.kt index cf4b7bb255..4dfcad0c86 100644 --- a/service/src/main/kotlin/no/nav/su/se/bakover/service/klage/KlagevedtakServiceImpl.kt +++ b/service/src/main/kotlin/no/nav/su/se/bakover/service/klage/KlagevedtakServiceImpl.kt @@ -1,13 +1,138 @@ package no.nav.su.se.bakover.service.klage +import arrow.core.Either +import no.nav.su.se.bakover.common.persistence.SessionFactory +import no.nav.su.se.bakover.domain.journal.JournalpostId +import no.nav.su.se.bakover.domain.klage.KanIkkeTolkeKlagevedtak +import no.nav.su.se.bakover.domain.klage.KlageRepo import no.nav.su.se.bakover.domain.klage.KlagevedtakRepo +import no.nav.su.se.bakover.domain.klage.KlagevedtakUtfall +import no.nav.su.se.bakover.domain.klage.OversendtKlage import no.nav.su.se.bakover.domain.klage.UprosessertFattetKlagevedtak +import no.nav.su.se.bakover.domain.klage.UprosessertKlagevedtak +import no.nav.su.se.bakover.domain.oppgave.OppgaveConfig +import no.nav.su.se.bakover.service.oppgave.OppgaveService +import no.nav.su.se.bakover.service.person.PersonService +import org.slf4j.LoggerFactory +import java.time.Clock +import java.util.UUID class KlagevedtakServiceImpl( private val klagevedtakRepo: KlagevedtakRepo, + private val klageRepo: KlageRepo, + private val oppgaveService: OppgaveService, + private val personService: PersonService, + private val sessionFactory: SessionFactory, ) : KlagevedtakService { + private val log = LoggerFactory.getLogger(this::class.java) override fun lagre(klageVedtak: UprosessertFattetKlagevedtak) { klagevedtakRepo.lagre(klageVedtak) } + + override fun håndterUtfallFraKlageinstans(deserializeAndMap: (id: UUID, json: String) -> Either) { + val ubehandletKlagevedtak = klagevedtakRepo.hentUbehandlaKlagevedtak() + + ubehandletKlagevedtak.forEach { uprosessertFattetKlagevedtak -> + deserializeAndMap(uprosessertFattetKlagevedtak.id, uprosessertFattetKlagevedtak.metadata.value) + .tapLeft { + log.error( + "Deserializering av melding fra Klageinstans feilet for klagevedtak med hendelseId: ${uprosessertFattetKlagevedtak.metadata.hendelseId}", + it, + ) + klagevedtakRepo.markerSomFeil(uprosessertFattetKlagevedtak.id) + } + .tap { prosesserKlagevedtak(it) } + } + } + + private fun prosesserKlagevedtak(klagevedtak: UprosessertKlagevedtak) { + val klage = klageRepo.hentKlage(klagevedtak.klageId) + + if (klage == null) { + log.error("Kunne ikke prosessere melding fra Klageinstans. Fant ikke klage med klageId: ${klagevedtak.klageId}") + return klagevedtakRepo.markerSomFeil(klagevedtak.id) + } + + // TODO ai: Flytt over til Klage når vi har ett konsept om vedtak i Klage. + if (klage !is OversendtKlage) { + log.error("Kunne ikke prosessere melding fra Klageinstans. Feil skjedde ved uthenting av klagen, forventet ${OversendtKlage::class.java.name} men var ${klage::class.java.name}") + return klagevedtakRepo.markerSomFeil(klagevedtak.id) + } + + when (klagevedtak.utfall) { + KlagevedtakUtfall.STADFESTELSE -> håndterStadfestelse(klagevedtak, klage) + KlagevedtakUtfall.RETUR -> håndterRetur(klagevedtak, klage) + KlagevedtakUtfall.TRUKKET, + KlagevedtakUtfall.OPPHEVET, + KlagevedtakUtfall.MEDHOLD, + KlagevedtakUtfall.DELVIS_MEDHOLD, + KlagevedtakUtfall.UGUNST, + KlagevedtakUtfall.AVVIST, + -> { + /* + * Desse lagres som FEIL i databasen uten videre håndtering. Tanken er att vi får håndtere + * de casene som intreffer og så må vi manuellt putte de til 'UPROSSESERT' vid senere tidspunkt. + * */ + log.error("Utfall: ${klagevedtak.utfall} fra Klageinstans er ikke håndtert.") + klagevedtakRepo.markerSomFeil(klagevedtak.id) + } + } + } + + private fun håndterRetur(klagevedtak: UprosessertKlagevedtak, klage: OversendtKlage) { + lagOppgaveConfig(klagevedtak, klage).map { oppgaveConfig -> + oppgaveService.opprettOppgave(oppgaveConfig).map { oppgaveId -> + sessionFactory.withTransactionContext { tx -> + klageRepo.lagre(klage.copy(oppgaveId = oppgaveId), tx) + klagevedtakRepo.lagre(klagevedtak.tilProsessert(oppgaveId), tx) + } + } + } + } + + private fun håndterStadfestelse(klagevedtak: UprosessertKlagevedtak, klage: OversendtKlage) { + lagOppgaveConfig(klagevedtak, klage).map { oppgaveConfig -> + oppgaveService.opprettOppgave(oppgaveConfig).map { oppgaveId -> + klagevedtakRepo.lagre(klagevedtak.tilProsessert(oppgaveId)) + } + } + } + + private fun lagOppgaveConfig( + klagevedtak: UprosessertKlagevedtak, + klage: OversendtKlage, + ): Either { + return personService.hentAktørIdMedSystembruker(klage.fnr).map { aktørId -> + when (klagevedtak.utfall) { + KlagevedtakUtfall.TRUKKET, + KlagevedtakUtfall.AVVIST, + KlagevedtakUtfall.STADFESTELSE -> OppgaveConfig.Klage.Vedtak.Informasjon( + saksnummer = klage.saksnummer, + aktørId = aktørId, + journalpostId = JournalpostId(klagevedtak.vedtaksbrevReferanse), + tilordnetRessurs = null, + clock = Clock.systemUTC(), + utfall = klagevedtak.utfall + ) + KlagevedtakUtfall.RETUR, + KlagevedtakUtfall.OPPHEVET, + KlagevedtakUtfall.MEDHOLD, + KlagevedtakUtfall.DELVIS_MEDHOLD, + KlagevedtakUtfall.UGUNST -> OppgaveConfig.Klage.Vedtak.Handling( + saksnummer = klage.saksnummer, + aktørId = aktørId, + journalpostId = JournalpostId(klagevedtak.vedtaksbrevReferanse), + tilordnetRessurs = null, + clock = Clock.systemUTC(), + utfall = klagevedtak.utfall + ) + } + }.mapLeft { + KunneIkkeHenteAktørId + } + } } + +object KunneIkkeHenteAktørId +object KunneIkkeOppretteOppgave diff --git a/service/src/test/kotlin/no/nav/su/se/bakover/service/klage/KlagevedtakServiceImplTest.kt b/service/src/test/kotlin/no/nav/su/se/bakover/service/klage/KlagevedtakServiceImplTest.kt new file mode 100644 index 0000000000..9531c94395 --- /dev/null +++ b/service/src/test/kotlin/no/nav/su/se/bakover/service/klage/KlagevedtakServiceImplTest.kt @@ -0,0 +1,170 @@ +package no.nav.su.se.bakover.service.klage + +import arrow.core.left +import arrow.core.right +import io.kotest.matchers.shouldBe +import no.nav.su.se.bakover.domain.AktørId +import no.nav.su.se.bakover.domain.journal.JournalpostId +import no.nav.su.se.bakover.domain.klage.KanIkkeTolkeKlagevedtak +import no.nav.su.se.bakover.domain.klage.KlageRepo +import no.nav.su.se.bakover.domain.klage.KlagevedtakRepo +import no.nav.su.se.bakover.domain.klage.KlagevedtakUtfall +import no.nav.su.se.bakover.domain.klage.UprosessertFattetKlagevedtak +import no.nav.su.se.bakover.domain.klage.UprosessertKlagevedtak +import no.nav.su.se.bakover.domain.oppgave.OppgaveConfig +import no.nav.su.se.bakover.domain.oppgave.OppgaveId +import no.nav.su.se.bakover.service.argThat +import no.nav.su.se.bakover.service.oppgave.OppgaveService +import no.nav.su.se.bakover.service.person.PersonService +import no.nav.su.se.bakover.test.TestSessionFactory +import no.nav.su.se.bakover.test.fixedTidspunkt +import no.nav.su.se.bakover.test.oversendtKlage +import org.junit.jupiter.api.Test +import org.mockito.kotlin.any +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.mock +import org.mockito.kotlin.verify +import java.time.Clock +import java.util.UUID + +internal class KlagevedtakServiceImplTest { + val klage = oversendtKlage().second + + @Test + fun `når deserializering feiler så markerer vi vedtaket med FEIL`() { + val id = UUID.randomUUID() + val klagevedtakRepoMock: KlagevedtakRepo = mock { + on { hentUbehandlaKlagevedtak() } doReturn listOf(uprosessertFattetKlagevedtak(id)) + } + + buildKlagevedtakService(klagevedtakRepoMock).håndterUtfallFraKlageinstans { _, _ -> KanIkkeTolkeKlagevedtak.KunneIkkeDeserialisere.left() } + verify(klagevedtakRepoMock).markerSomFeil(argThat { it shouldBe id }) + } + + @Test + fun `stadfestelse setter vedtaket som prosessert`() { + val id = UUID.randomUUID() + val klagevedtakRepoMock: KlagevedtakRepo = mock { + on { hentUbehandlaKlagevedtak() } doReturn listOf(uprosessertFattetKlagevedtak(id)) + } + val klageRepoMock: KlageRepo = mock { + on { hentKlage(any()) } doReturn klage + on { defaultTransactionContext() } doReturn TestSessionFactory.transactionContext + } + val personServiceMock: PersonService = mock { + on { hentAktørIdMedSystembruker(any()) } doReturn AktørId("").right() + } + val oppgaveServiceMock: OppgaveService = mock { + on { opprettOppgave(any()) } doReturn OppgaveId("212121").right() + } + val mappedKlagevedtak = UprosessertKlagevedtak( + id = id, + eventId = UUID.randomUUID().toString(), + klageId = klage.id, + utfall = KlagevedtakUtfall.STADFESTELSE, + vedtaksbrevReferanse = "123456", + ) + + buildKlagevedtakService( + klagevedtakRepo = klagevedtakRepoMock, + klageRepo = klageRepoMock, + personService = personServiceMock, + oppgaveService = oppgaveServiceMock, + ).håndterUtfallFraKlageinstans { _, _ -> mappedKlagevedtak.right() } + verify(klagevedtakRepoMock).hentUbehandlaKlagevedtak() + verify(klageRepoMock).hentKlage(argThat { it shouldBe klage.id }) + verify(personServiceMock).hentAktørIdMedSystembruker(klage.fnr) + verify(oppgaveServiceMock).opprettOppgave( + argThat { + it shouldBe OppgaveConfig.Klage.Vedtak.Informasjon( + saksnummer = klage.saksnummer, + aktørId = AktørId(aktørId = ""), + journalpostId = JournalpostId(value = mappedKlagevedtak.vedtaksbrevReferanse), + tilordnetRessurs = null, + clock = Clock.systemUTC(), + utfall = KlagevedtakUtfall.STADFESTELSE + ) + }, + ) + verify(klagevedtakRepoMock).lagre(mappedKlagevedtak.tilProsessert(OppgaveId("212121"))) + } + + @Test + fun `RETUR setter vedtaket som prosessert og lager ny oppgave for klagen`() { + val id = UUID.randomUUID() + val klagevedtakRepoMock: KlagevedtakRepo = mock { + on { hentUbehandlaKlagevedtak() } doReturn listOf(uprosessertFattetKlagevedtak(id)) + } + val klageRepoMock: KlageRepo = mock { + on { hentKlage(any()) } doReturn klage + on { defaultTransactionContext() } doReturn TestSessionFactory.transactionContext + } + val personServiceMock: PersonService = mock { + on { hentAktørIdMedSystembruker(any()) } doReturn AktørId("").right() + } + val oppgaveServiceMock: OppgaveService = mock { + on { opprettOppgave(any()) } doReturn OppgaveId("212121").right() + } + val mappedKlagevedtak = UprosessertKlagevedtak( + id = id, + eventId = UUID.randomUUID().toString(), + klageId = klage.id, + utfall = KlagevedtakUtfall.RETUR, + vedtaksbrevReferanse = "123456", + ) + + buildKlagevedtakService( + klagevedtakRepo = klagevedtakRepoMock, + klageRepo = klageRepoMock, + personService = personServiceMock, + oppgaveService = oppgaveServiceMock, + ).håndterUtfallFraKlageinstans { _, _ -> mappedKlagevedtak.right() } + verify(klagevedtakRepoMock).hentUbehandlaKlagevedtak() + verify(klageRepoMock).hentKlage(argThat { it shouldBe klage.id }) + verify(personServiceMock).hentAktørIdMedSystembruker(klage.fnr) + verify(oppgaveServiceMock).opprettOppgave( + argThat { + it shouldBe OppgaveConfig.Klage.Vedtak.Handling( + saksnummer = klage.saksnummer, + aktørId = AktørId(aktørId = ""), + journalpostId = JournalpostId(value = "123456"), + tilordnetRessurs = null, + clock = Clock.systemUTC(), + utfall = KlagevedtakUtfall.RETUR + ) + }, + ) + TestSessionFactory().withTransactionContext { tx -> + verify(klageRepoMock).lagre(klage.copy(oppgaveId = OppgaveId("212121")), tx) + verify(klagevedtakRepoMock).lagre(mappedKlagevedtak.tilProsessert(OppgaveId("212121")), tx) + } + } + + private fun uprosessertFattetKlagevedtak(id: UUID) = UprosessertFattetKlagevedtak( + id = id, + opprettet = fixedTidspunkt, + metadata = UprosessertFattetKlagevedtak.Metadata( + hendelseId = "55", + offset = 0, + partisjon = 0, + key = "", + value = "", + ), + ) + + private fun buildKlagevedtakService( + klagevedtakRepo: KlagevedtakRepo = mock(), + klageRepo: KlageRepo = mock(), + oppgaveService: OppgaveService = mock(), + personService: PersonService = mock(), + sessionFactory: TestSessionFactory = TestSessionFactory(), + ): KlagevedtakService { + return KlagevedtakServiceImpl( + klagevedtakRepo = klagevedtakRepo, + klageRepo = klageRepo, + oppgaveService = oppgaveService, + personService = personService, + sessionFactory = sessionFactory + ) + } +} diff --git a/web/src/main/kotlin/no/nav/su/se/bakover/web/Application.kt b/web/src/main/kotlin/no/nav/su/se/bakover/web/Application.kt index 5a1d0c5fa6..76075747a0 100644 --- a/web/src/main/kotlin/no/nav/su/se/bakover/web/Application.kt +++ b/web/src/main/kotlin/no/nav/su/se/bakover/web/Application.kt @@ -80,6 +80,7 @@ import no.nav.su.se.bakover.web.services.avstemming.GrensesnittsavstemingJob import no.nav.su.se.bakover.web.services.avstemming.KonsistensavstemmingJob import no.nav.su.se.bakover.web.services.dokument.DistribuerDokumentJob import no.nav.su.se.bakover.web.services.klage.FattetKlagevedtakConsumer +import no.nav.su.se.bakover.web.services.klage.KlagevedtakJob import no.nav.su.se.bakover.web.services.personhendelser.PersonhendelseConsumer import no.nav.su.se.bakover.web.services.personhendelser.PersonhendelseOppgaveJob import no.nav.su.se.bakover.web.services.utbetaling.kvittering.LokalKvitteringJob @@ -307,6 +308,8 @@ fun Application.susebakover( jobConfig = applicationConfig.jobConfig.konsistensavstemming, clock = clock, ).schedule() + + KlagevedtakJob(klagevedtakService = services.klagevedtakService, leaderPodLookup = clients.leaderPodLookup).schedule() } else if (applicationConfig.runtimeEnvironment == ApplicationConfig.RuntimeEnvironment.Local) { LokalKvitteringJob(LokalKvitteringService(databaseRepos.utbetaling, utbetalingKvitteringConsumer)).schedule() @@ -333,6 +336,8 @@ fun Application.susebakover( leaderPodLookup = clients.leaderPodLookup, intervall = applicationConfig.jobConfig.personhendelse.intervall, ).schedule() + + KlagevedtakJob(klagevedtakService = services.klagevedtakService, leaderPodLookup = clients.leaderPodLookup).schedule() } fun Route.withAccessProtectedServices( diff --git a/web/src/main/kotlin/no/nav/su/se/bakover/web/services/klage/FattetKlagevedtak.kt b/web/src/main/kotlin/no/nav/su/se/bakover/web/services/klage/FattetKlagevedtak.kt index 1fd886dce9..1e6f7a403d 100644 --- a/web/src/main/kotlin/no/nav/su/se/bakover/web/services/klage/FattetKlagevedtak.kt +++ b/web/src/main/kotlin/no/nav/su/se/bakover/web/services/klage/FattetKlagevedtak.kt @@ -25,7 +25,7 @@ internal data class FattetKlagevedtak( */ val utfall: String, /** Journalpost id til vedtaksbrev. */ - val vedtaksbrevReferanse: String?, + val vedtaksbrevReferanse: String, /** Intern referanse fra kabal. Er per i dag vedtak_id i deres database. Kan i fremtiden brukes for å hente data om vedtak fra Kabal (se Swagger doc) */ val kabalReferanse: String, ) diff --git a/web/src/main/kotlin/no/nav/su/se/bakover/web/services/klage/KlagevedtakJob.kt b/web/src/main/kotlin/no/nav/su/se/bakover/web/services/klage/KlagevedtakJob.kt new file mode 100644 index 0000000000..80c8620187 --- /dev/null +++ b/web/src/main/kotlin/no/nav/su/se/bakover/web/services/klage/KlagevedtakJob.kt @@ -0,0 +1,70 @@ +package no.nav.su.se.bakover.web.services.klage + +import arrow.core.Either +import arrow.core.flatMap +import no.nav.su.se.bakover.common.deserialize +import no.nav.su.se.bakover.domain.klage.KanIkkeTolkeKlagevedtak +import no.nav.su.se.bakover.domain.klage.KlagevedtakUtfall +import no.nav.su.se.bakover.domain.klage.UprosessertKlagevedtak +import no.nav.su.se.bakover.domain.nais.LeaderPodLookup +import no.nav.su.se.bakover.service.klage.KlagevedtakService +import no.nav.su.se.bakover.web.services.erLeaderPod +import org.slf4j.LoggerFactory +import java.net.InetAddress +import java.time.Duration +import java.time.temporal.ChronoUnit +import java.util.UUID +import kotlin.concurrent.fixedRateTimer + +class KlagevedtakJob( + private val klagevedtakService: KlagevedtakService, + private val leaderPodLookup: LeaderPodLookup, +) { + private val log = LoggerFactory.getLogger(this::class.java) + private val jobName = "Håndter utfall fra Klageinstans" + private val periode = Duration.of(10, ChronoUnit.MINUTES).toMillis() + + private val hostName = InetAddress.getLocalHost().hostName + + companion object { + fun mapper(id: UUID, json: String): Either = + Either.catch { + deserialize(json) + }.mapLeft { KanIkkeTolkeKlagevedtak.KunneIkkeDeserialisere } + .flatMap { klagevedtak -> + Either.catch { + UprosessertKlagevedtak( + id = id, + eventId = klagevedtak.eventId, + utfall = KlagevedtakUtfall.valueOf(klagevedtak.utfall), + klageId = UUID.fromString(klagevedtak.kildeReferanse), + vedtaksbrevReferanse = klagevedtak.vedtaksbrevReferanse, + ) + }.mapLeft { KanIkkeTolkeKlagevedtak.UgyldigeVerdier } + } + } + + fun schedule() { + log.info( + "Starter skeduleringsjobb '$jobName' med intervall: $periode ms. Mitt hostnavn er $hostName. Jeg er ${ + if (leaderPodLookup.erLeaderPod(hostname = hostName)) "" else "ikke " + }leder.", + ) + + fixedRateTimer( + name = jobName, + daemon = true, + period = periode, + ) { + Either.catch { + if (leaderPodLookup.erLeaderPod(hostname = hostName)) { + log.debug("Kjører skeduleringsjobb '$jobName'") + klagevedtakService.håndterUtfallFraKlageinstans(::mapper) + log.debug("Fullførte skeduleringsjobb '$jobName'") + } + }.mapLeft { + log.error("Skeduleringsjobb '$jobName' feilet med stacktrace:", it) + } + } + } +} diff --git a/web/src/test/kotlin/no/nav/su/se/bakover/web/services/klage/KlagevedtakJobTest.kt b/web/src/test/kotlin/no/nav/su/se/bakover/web/services/klage/KlagevedtakJobTest.kt new file mode 100644 index 0000000000..6ad5640874 --- /dev/null +++ b/web/src/test/kotlin/no/nav/su/se/bakover/web/services/klage/KlagevedtakJobTest.kt @@ -0,0 +1,46 @@ +package no.nav.su.se.bakover.web.services.klage + +import arrow.core.right +import io.kotest.matchers.shouldBe +import no.nav.su.se.bakover.domain.klage.KlagevedtakUtfall +import no.nav.su.se.bakover.domain.klage.UprosessertKlagevedtak +import org.junit.jupiter.api.Test +import java.util.UUID + +internal class KlagevedtakJobTest { + + @Test + fun `deserializerer klagevedtak-melding`() { + val klagevedtakId = UUID.randomUUID() + + val eventId = UUID.randomUUID() + val kildeReferanse = UUID.randomUUID() + val kabalReferanse = UUID.randomUUID() + + KlagevedtakJob.mapper( + klagevedtakId, + jsonMelding( + eventId = eventId, + kildeReferanse = kildeReferanse, + kabalReferanse = kabalReferanse, + vedtaksbrevReferanse = "210219347" + ) + ) shouldBe UprosessertKlagevedtak( + id = klagevedtakId, + eventId = eventId.toString(), + klageId = kildeReferanse, + utfall = KlagevedtakUtfall.STADFESTELSE, + vedtaksbrevReferanse = "210219347", + ).right() + } + + private fun jsonMelding( + eventId: UUID, + kildeReferanse: UUID, + kabalReferanse: UUID, + vedtaksbrevReferanse: String, + ) = + """ + {"eventId":"$eventId","kildeReferanse":"$kildeReferanse","kilde":"SUPSTONAD","utfall":"STADFESTELSE","vedtaksbrevReferanse":"$vedtaksbrevReferanse","kabalReferanse":"$kabalReferanse"} + """.trimIndent() +}