Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EY-4842: Opprettet service for utsending av brev #6601

Merged
merged 5 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ data class Arbeidsjobb(
val merknad: String? = null,
val opprettet: Tidspunkt,
val sistEndret: Tidspunkt,
)
) {
fun oppdaterStatus(status: ArbeidStatus): Arbeidsjobb = this.copy(status = status)
}

fun lagNyArbeidsJobb(
sakId: SakId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import no.nav.etterlatte.common.ConnectionAutoclosing
import no.nav.etterlatte.libs.common.sak.SakId
import no.nav.etterlatte.libs.common.tidspunkt.getTidspunkt
import no.nav.etterlatte.libs.common.tidspunkt.setTidspunkt
import no.nav.etterlatte.libs.database.singleOrNull
import no.nav.etterlatte.libs.database.toList
import org.slf4j.LoggerFactory
import java.sql.ResultSet
Expand All @@ -14,7 +15,7 @@ class ArbeidstabellDao(
) {
private val logger = LoggerFactory.getLogger(this::class.java)

fun opprettJobb(jobb: Arbeidsjobb) {
fun opprettJobb(jobb: Arbeidsjobb): Arbeidsjobb {
connectionAutoclosing.hentConnection {
with(it) {
val statement =
Expand All @@ -36,9 +37,48 @@ class ArbeidstabellDao(
logger.info("Opprettet en jobb av type ${jobb.type.name} for sak ${jobb.sakId} med status ${jobb.status}")
}
}

return hentJobb(jobb.id) ?: throw IllegalStateException("Fant ikke jobb $jobb")
}

fun oppdaterJobb(jobb: Arbeidsjobb): Arbeidsjobb {
connectionAutoclosing.hentConnection {
with(it) {
val statement =
prepareStatement(
"""
UPDATE arbeidstabell SET status = ? WHERE id = ?
""".trimIndent(),
)
statement.setString(1, jobb.status.name)
statement.setObject(2, jobb.id)
statement.executeUpdate()
}
}
return hentJobb(jobb.id) ?: throw IllegalStateException("Fant ikke jobb $jobb")
}

fun hentKlareJobber(): List<Arbeidsjobb> =
fun hentJobb(id: UUID): Arbeidsjobb? =
connectionAutoclosing.hentConnection {
with(it) {
val statement =
prepareStatement(
"""
SELECT * from arbeidstabell where id = ?
""".trimIndent(),
)
statement.setObject(1, id)
statement.executeQuery().singleOrNull {
asJobb()
}
}
}

fun hentKlareJobber(
antallSaker: Long,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

todo: mangler limit i spørringen her

ekskluderteSaker: List<Long> = emptyList(),
): List<Arbeidsjobb> =
// TODO håndter antall saker og ekskluderte saker
connectionAutoclosing.hentConnection {
with(it) {
val statement =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package no.nav.etterlatte.behandling.jobs.brevjobber

import no.nav.etterlatte.Context
import no.nav.etterlatte.Kontekst
import no.nav.etterlatte.behandling.jobs.brevjobber.ArbeidStatus.FEILET
import no.nav.etterlatte.behandling.jobs.brevjobber.ArbeidStatus.FERDIG
import no.nav.etterlatte.behandling.jobs.brevjobber.ArbeidStatus.PAAGAAENDE
import no.nav.etterlatte.inTransaction
import no.nav.etterlatte.libs.ktor.token.BrukerTokenInfo
import no.nav.etterlatte.libs.ktor.token.HardkodaSystembruker
import org.slf4j.LoggerFactory

class BrevutsendelseJob(
private val arbeidstabellDao: ArbeidstabellDao,
private val brevutsendelseService: BrevutsendelseService,
) {
private val saksbehandler: BrukerTokenInfo = HardkodaSystembruker.oppgave

private val logger = LoggerFactory.getLogger(this::class.java)

fun setupKontekstAndRun(context: Context) {
Kontekst.set(context)
run()
}

// TODO er jobber egentlig riktig begrep her - blir kanskje litt for generelt i denne sammenheng? Burde tabellen heller hete brevutsendelse?
// TODO mer logging må på plass
private fun run() {
logger.info("Starter jobb for masseutsendelse av brev")

val brevutsendelser = inTransaction { arbeidstabellDao.hentKlareJobber(ANTALL_SAKER, EKSKLUDERTE_SAKER) }
logger.info("Hentet ${brevutsendelser.size} brevutsendelser som er klare for prosessering")

brevutsendelser.forEach { brevutsendelse ->
try {
inTransaction {
val paagaaendeBrevutsendelse = oppdaterStatus(brevutsendelse, PAAGAAENDE)
brevutsendelseService.prosesserBrevutsendelse(paagaaendeBrevutsendelse, saksbehandler)
oppdaterStatus(paagaaendeBrevutsendelse, FERDIG)
}
} catch (e: Exception) {
// TODO ønsker nok å lagre ned exception her
oppdaterStatus(brevutsendelse, FEILET)
logger.error("Feilet under brevutsendelse av type ${brevutsendelse.type.name} for sak ${brevutsendelse.sakId}", e)
}
}
}

// TODO oppdater status eller opprett ny rad for hver status?
private fun oppdaterStatus(
jobb: Arbeidsjobb,
status: ArbeidStatus,
) = arbeidstabellDao.oppdaterJobb(jobb.oppdaterStatus(status))

// TODO burde dette heller håndteres fra en tabell slik at det kan oppdateres uten å endre koden?
companion object {
val ANTALL_SAKER: Long = 1
val EKSKLUDERTE_SAKER: List<Long> = emptyList()
}
Comment on lines +55 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternativ nais config?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

trenger vi ekskluderte saker egentlig? hvis ikke bare å droppe det vel er bare unødvendig kompleksitet

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package no.nav.etterlatte.behandling.jobs.brevjobber

import kotlinx.coroutines.runBlocking
import no.nav.etterlatte.behandling.jobs.brevjobber.SjekkGyldigBrevMottakerResultat.GYLDIG_MOTTAKER
import no.nav.etterlatte.behandling.klienter.BrevApiKlient
import no.nav.etterlatte.brev.BrevParametre
import no.nav.etterlatte.brev.SaksbehandlerOgAttestant
import no.nav.etterlatte.brev.model.FerdigstillJournalFoerOgDistribuerOpprettetBrev
import no.nav.etterlatte.brev.model.Spraak
import no.nav.etterlatte.libs.common.feilhaandtering.InternfeilException
import no.nav.etterlatte.libs.common.logging.withLogContext
import no.nav.etterlatte.libs.common.oppgave.OppgaveIntern
import no.nav.etterlatte.libs.common.oppgave.OppgaveType
import no.nav.etterlatte.libs.common.person.maskerFnr
import no.nav.etterlatte.libs.common.sak.Sak
import no.nav.etterlatte.libs.ktor.token.BrukerTokenInfo
import no.nav.etterlatte.oppgave.OppgaveService
import no.nav.etterlatte.sak.SakService
import org.slf4j.LoggerFactory

class BrevutsendelseService(
private val sakService: SakService,
private val oppgaveService: OppgaveService,
private val sjekkBrevMottakerService: SjekkBrevMottakerService,
private val brevKlient: BrevApiKlient,
) {
private val logger = LoggerFactory.getLogger(this::class.java)

fun prosesserBrevutsendelse(
brevutsendelse: Arbeidsjobb,
brukerTokenInfo: BrukerTokenInfo,
) {
withLogContext(kv = mdcVerdier(brevutsendelse)) {
logger.info("Starter å prosessering brevutsendelse av type ${brevutsendelse.type.name} for sak ${brevutsendelse.sakId}")

val sak = hentSak(brevutsendelse)
val gyldigBrevMottakerResultat = sjekkBrevMottakerService.sjekkOmPersonErGyldigBrevmottaker(sak, brukerTokenInfo)

if (gyldigBrevMottakerResultat == GYLDIG_MOTTAKER) {
sendBrev(sak, brevutsendelse, brukerTokenInfo)
// TODO hvis noe feiler her, må manuell oppgave opprettes
} else {
logger.info("Manuell oppgave opprettes i sak ${sak.id} fordi mottaker ikke var gyldig: ${gyldigBrevMottakerResultat.name}")
opprettManuellOppgave(brevutsendelse)
}

logger.info("Prosessering av brevutsendelse i sak ${brevutsendelse.sakId} er fullført")

// TODO burde returnere et objekt her som sier noe om hva som ble resultatet (brev sendt, manuell oppgave opprettet etc)
}
}

private fun mdcVerdier(brevutsendelse: Arbeidsjobb) =
mapOf(
"sakId" to brevutsendelse.sakId.toString(),
"jobbType" to brevutsendelse.type.name,
)

private fun hentSak(brevutsendelse: Arbeidsjobb) =
(
sakService.finnSak(brevutsendelse.sakId)
?: throw InternfeilException("Fant ikke sak med id ${brevutsendelse.sakId} for brevutsendelse")
)

private fun sendBrev(
sak: Sak,
brevutsendelse: Arbeidsjobb,
saksbehandler: BrukerTokenInfo,
): Boolean {
logger.info("Sender brev til ${sak.ident.maskerFnr()} i sak ${sak.id}")

val tomtBrev = BrevParametre.TomtBrev(Spraak.NB) // TODO placeholder inntil vi har en brevmal

runBlocking {
val opprettetBrev = brevKlient.opprettSpesifiktBrev(brevutsendelse.sakId, tomtBrev, saksbehandler)
val ferdigstiltBrev =
brevKlient.ferdigstillBrev(
FerdigstillJournalFoerOgDistribuerOpprettetBrev(
opprettetBrev.id,
brevutsendelse.sakId,
sak.enhet,
SaksbehandlerOgAttestant(saksbehandler.ident(), saksbehandler.ident()),
),
saksbehandler,
)
logger.info("Brev med id ${ferdigstiltBrev.brevId} er ferdigstilt")

val journalfoertBrev = brevKlient.journalfoerBrev(brevutsendelse.sakId, ferdigstiltBrev.brevId, saksbehandler)
logger.info("Brev med id ${ferdigstiltBrev.brevId} er journaltført (journalpostId: ${journalfoertBrev.journalpostId})")

val distribuertBrev = brevKlient.distribuerBrev(brevutsendelse.sakId, ferdigstiltBrev.brevId, saksbehandler)
logger.info("Brev med id ${ferdigstiltBrev.brevId} er distribuert (bestillingsId: ${distribuertBrev.bestillingsId})")
}

return true
}

private fun opprettManuellOppgave(brevutsendelse: Arbeidsjobb): OppgaveIntern =
oppgaveService.opprettOppgave(
referanse = brevutsendelse.id.toString(),
sakId = brevutsendelse.sakId,
kilde = null, // TODO legge inn kilde
type = OppgaveType.GENERELL_OPPGAVE, // TODO er dette riktig
merknad = "Her må det komme tekst om hva saksbehandler må gjøre", // TODO hva skal stå her?
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ enum class JobbType(
val kategori: JobbKategori,
val sakType: SakType?,
) {
TREKKPLIKT_2025("Trekkplikt 2025, du taper penger på dette", JobbKategori.TREKKPLIKT, SakType.OMSTILLINGSSTOENAD),
TREKKPLIKT_2025("Trekkplikt 2025, du taper penger på dette", JobbKategori.TREKKPLIKT, SakType.BARNEPENSJON),
}

enum class JobbKategori {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package no.nav.etterlatte.behandling.jobs.brevjobber

import kotlinx.coroutines.runBlocking
import no.nav.etterlatte.behandling.GrunnlagService
import no.nav.etterlatte.behandling.jobs.brevjobber.SjekkGyldigBrevMottakerResultat.GYLDIG_MOTTAKER
import no.nav.etterlatte.behandling.jobs.brevjobber.SjekkGyldigBrevMottakerResultat.UGYLDIG_MOTTAKER_UTDATERTE_PERSON_OPPLYSNINGER
import no.nav.etterlatte.behandling.jobs.brevjobber.SjekkGyldigBrevMottakerResultat.UGYLDIG_MOTTAKER_UTDATERT_IDENT
import no.nav.etterlatte.behandling.jobs.brevjobber.SjekkGyldigBrevMottakerResultat.UGYLDIG_MOTTAKER_VERGEMAAL
import no.nav.etterlatte.common.klienter.PdlTjenesterKlient
import no.nav.etterlatte.libs.common.behandling.SakType
import no.nav.etterlatte.libs.common.feilhaandtering.InternfeilException
import no.nav.etterlatte.libs.common.logging.sikkerlogger
import no.nav.etterlatte.libs.common.person.PdlIdentifikator
import no.nav.etterlatte.libs.common.person.Person
import no.nav.etterlatte.libs.common.person.PersonRolle
import no.nav.etterlatte.libs.common.person.VergemaalEllerFremtidsfullmakt
import no.nav.etterlatte.libs.common.person.maskerFnr
import no.nav.etterlatte.libs.common.sak.Sak
import no.nav.etterlatte.libs.ktor.token.BrukerTokenInfo
import org.slf4j.LoggerFactory
import java.util.UUID

enum class SjekkGyldigBrevMottakerResultat {
UGYLDIG_MOTTAKER_UTDATERT_IDENT,
UGYLDIG_MOTTAKER_UTDATERTE_PERSON_OPPLYSNINGER,
UGYLDIG_MOTTAKER_VERGEMAAL,
GYLDIG_MOTTAKER,
}

class SjekkBrevMottakerService(
private val grunnlagService: GrunnlagService,
private val pdlTjenesterKlient: PdlTjenesterKlient,
) {
private val logger = LoggerFactory.getLogger(this::class.java)

fun sjekkOmPersonErGyldigBrevmottaker(
sak: Sak,
brukerTokenInfo: BrukerTokenInfo,
): SjekkGyldigBrevMottakerResultat {
logger.info("Sjekker om person ${sak.ident.maskerFnr()} er en gyldig brev mottaker")
sikkerlogger().info("Sjekker om person ${sak.ident} er en gyldig brev mottaker")

// TODO Må vel ha en iverksatt sak for at dette skal sendes?

// Sjekker ident
hentPdlPersonident(sak).let { sisteIdentifikatorPdl ->
val sisteIdent =
when (sisteIdentifikatorPdl) {
is PdlIdentifikator.FolkeregisterIdent -> sisteIdentifikatorPdl.folkeregisterident.value
is PdlIdentifikator.Npid -> sisteIdentifikatorPdl.npid.ident
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flyten vil vel generelt ikke fungere dersom ident er NPID? 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skal vi støtte det ?

}
if (sak.ident != sisteIdent) {
return UGYLDIG_MOTTAKER_UTDATERT_IDENT
}
}

val opplysningerPdl = hentPdlPersonopplysning(sak)
val opplysningerGjenny = hentOpplysningerGjenny(sak, brukerTokenInfo)

// Sjekker vergemål
if (!opplysningerPdl.vergemaalEllerFremtidsfullmakt.isNullOrEmpty()) {
return UGYLDIG_MOTTAKER_VERGEMAAL
}

val opplysningerErUendretIPdl =
with(opplysningerGjenny) {
fornavn == opplysningerPdl.fornavn &&
mellomnavn == opplysningerPdl.mellomnavn &&
etternavn == opplysningerPdl.etternavn &&
foedselsdato == opplysningerPdl.foedselsdato &&
erLikeVergemaal(vergemaalEllerFremtidsfullmakt, opplysningerPdl.vergemaalEllerFremtidsfullmakt)
}

// Sjekker for utdaterte personopplysninger
if (!opplysningerErUendretIPdl) {
logger.info(
"Personopplysninger i PDL og Gjenny er forskjellig i sak ${sak.id}. " +
"Det opprettes en oppgave for manuell håndtering. Se sikkerlogg for detaljer om hva som er forskjellig.",
)
sikkerlogger().info(
"Personopplysninger i PDL og Gjenny er forskjellig i sak ${sak.id}. " +
"Personopplysinger i Grunnlag: $opplysningerGjenny, " +
"Personopplysinger i PDL: $opplysningerPdl",
)
return UGYLDIG_MOTTAKER_UTDATERTE_PERSON_OPPLYSNINGER
}

return GYLDIG_MOTTAKER
}

private fun hentPdlPersonident(sak: Sak) =
runBlocking {
pdlTjenesterKlient.hentPdlIdentifikator(sak.ident)
?: throw InternfeilException("Fant ikke ident fra PDL for sak ${sak.id}")
}

private fun hentPdlPersonopplysning(sak: Sak): Person {
// Mottaker av ytelsen
val rolle =
when (sak.sakType) {
SakType.OMSTILLINGSSTOENAD -> PersonRolle.GJENLEVENDE
SakType.BARNEPENSJON -> PersonRolle.BARN
}

return pdlTjenesterKlient
.hentPdlModellForSaktype(sak.ident, rolle, sak.sakType)
.toPerson()
}

private fun hentOpplysningerGjenny(
sak: Sak,
brukerTokenInfo: BrukerTokenInfo,
): Person =
runBlocking {
grunnlagService
.hentPersonopplysninger(
// TODO skal vi hente siste iverksatte behandling her, eller nytt endepunkt for siste grunnlag i sak?
UUID.randomUUID(),
sak.sakType,
brukerTokenInfo,
).soeker
?.opplysning ?: throw InternfeilException("Fant ikke opplysninger for sak=${sak.id}")
}

private fun erLikeVergemaal(
vergerEn: List<VergemaalEllerFremtidsfullmakt>?,
vergerTo: List<VergemaalEllerFremtidsfullmakt>?,
): Boolean {
if (vergerEn.isNullOrEmpty() && vergerTo.isNullOrEmpty()) {
return true
}
return vergerEn == vergerTo
}
}
Loading