Skip to content

Commit

Permalink
EY-4899 daglig jobb som lager oppfølgingsoppgaver (#6887)
Browse files Browse the repository at this point in the history
* Legger inn oppfølgingsoppgave hvis unntak uten frist

Ligger bak en featuretoggle, så funksjonaliteten kan gå ut i dev kun.

* Sender inn featuretoggleservice

* wip daglige jobber

* legge til service for håndtere opprette oppfølgsingsoppgaver

* rette byggefeil etter merge

* revert la til linjeskift pga ktlint

* wip

* opprette oppgave ved unntak utløper

* definere tomDato en plass

* Update apps/etterlatte-tidshendelser/src/main/kotlin/no/nav/etterlatte/tidshendelser/JobbScheduler.kt

Co-authored-by: Øyvind Stette Haarberg <[email protected]>

* Update apps/etterlatte-behandling/src/main/kotlin/behandling/aktivitetsplikt/AktivitetspliktDao.kt

Co-authored-by: Øyvind Stette Haarberg <[email protected]>

* fjerne todo

---------

Co-authored-by: Øyvind Haarberg <[email protected]>
  • Loading branch information
andreasbalevik and oyvindsh authored Feb 3, 2025
1 parent 79cf01a commit 0e93431
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ class TidshendelseService(
JobbType.REGULERING,
JobbType.FINN_SAKER_TIL_REGULERING,
JobbType.AARLIG_INNTEKTSJUSTERING,

-> throw InternfeilException("Skal ikke lage oppgave for jobbtype: ${hendelse.jobbtype}")
}

Expand All @@ -211,6 +212,7 @@ class TidshendelseService(
JobbType.REGULERING,
JobbType.FINN_SAKER_TIL_REGULERING,
JobbType.AARLIG_INNTEKTSJUSTERING,

-> throw InternfeilException("Skal ikke lage oppgave for jobbtype: $type")
}
}
Expand Down
1 change: 1 addition & 0 deletions apps/etterlatte-behandling/src/main/kotlin/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ private fun timerJobs(context: ApplicationContext): List<TimerJob> =
context.doedsmeldingerJob,
context.doedsmeldingerReminderJob,
context.saksbehandlerJob,
context.aktivitetspliktOppgaveUnntakUtloeperJob,
)

@Deprecated("Denne blir brukt i veldig mange testar. Bør rydde opp, men tar det etter denne endringa er inne")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,23 @@ class AktivitetspliktDao(
}
}

fun finnSakerKlarForOppfoelgingsoppgaveVarigUnntakUtloeper(tom: LocalDate) =
connectionAutoclosing.hentConnection {
with(it) {
val stmt =
prepareStatement(
"""
SELECT DISTINCT sak_id
FROM aktivitetsplikt_unntak
WHERE tom IS NOT NULL
AND tom <= ?
""".trimMargin(),
)
stmt.setDate(1, Date.valueOf(tom))
stmt.executeQuery().toList { SakId(getLong("sak_id")) }
}
}

private fun ResultSet.toAktivitet() =
AktivitetspliktAktivitetPeriode(
id = getUUID("id"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ enum class AktivitetspliktOppgaveToggles(
private val key: String,
) : FeatureToggle {
UNNTAK_UTEN_FRIST("aktivitetsplikt-oppgave-unntak-ingen-frist"),
UNNTAK_MED_FRIST("aktivitetsplikt-oppgave-unntak-med-frist"),
;

override fun key(): String = key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ class AktivitetspliktService(
separator = ", ",
prefix = "\"",
postfix = "\"",
) { it.unntak.navn }
) { it.unntak.lesbartNavn }
} har ikke til og med dato.",
frist =
LocalDate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ data class LagreAktivitetspliktUnntak(
)

enum class AktivitetspliktUnntakType(
val navn: String,
val lesbartNavn: String,
) {
OMSORG_BARN_UNDER_ETT_AAR("Omsorg for barn under ett år"),
OMSORG_BARN_SYKDOM("Omsorg for barn som har sykdom, skade eller funksjonshemming"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package no.nav.etterlatte.behandling.jobs

import no.nav.etterlatte.jobs.LoggerInfo
import no.nav.etterlatte.jobs.fixedRateCancellableTimer
import no.nav.etterlatte.libs.common.TimerJob
import org.slf4j.LoggerFactory
import java.time.Duration
import java.util.Timer

class AktivitetspliktOppgaveUnntakUtloeperJob(
private val aktivitetspliktOppgaveUnntakUtloeperJobService: AktivitetspliktOppgaveUnntakUtloeperJobService,
private val erLeader: () -> Boolean,
private val initialDelay: Long,
private val interval: Duration,
) : TimerJob {
private val logger = LoggerFactory.getLogger(this::class.java)
private val jobbNavn = this::class.simpleName

override fun schedule(): Timer {
logger.info("Starter jobb $jobbNavn med interval $interval")

return fixedRateCancellableTimer(
name = jobbNavn,
initialDelay = initialDelay,
loggerInfo = LoggerInfo(logger = logger, loggTilSikkerLogg = false),
period = interval.toMillis(),
) {
if (erLeader()) {
aktivitetspliktOppgaveUnntakUtloeperJobService.run()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package no.nav.etterlatte.behandling.jobs

import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.newSingleThreadContext
import kotlinx.coroutines.runBlocking
import no.nav.etterlatte.behandling.aktivitetsplikt.AktivitetspliktDao
import no.nav.etterlatte.behandling.aktivitetsplikt.AktivitetspliktOppgaveToggles
import no.nav.etterlatte.behandling.aktivitetsplikt.AktivitetspliktService
import no.nav.etterlatte.behandling.klienter.VedtakKlient
import no.nav.etterlatte.funksjonsbrytere.FeatureToggleService
import no.nav.etterlatte.libs.common.oppgave.OppgaveKilde
import no.nav.etterlatte.libs.common.oppgave.OppgaveType
import no.nav.etterlatte.libs.common.tidspunkt.toTidspunkt
import no.nav.etterlatte.libs.ktor.token.HardkodaSystembruker
import no.nav.etterlatte.oppgave.OppgaveService
import org.slf4j.LoggerFactory
import java.time.LocalDate

class AktivitetspliktOppgaveUnntakUtloeperJobService(
private val aktivitetspliktDao: AktivitetspliktDao,
private val aktivitetspliktService: AktivitetspliktService,
private val oppgaveService: OppgaveService,
private val vedtakKlient: VedtakKlient,
private val featureToggleService: FeatureToggleService,
) {
private val logger = LoggerFactory.getLogger(this::class.java)

@OptIn(DelicateCoroutinesApi::class)
fun run() {
if (featureToggleService.isEnabled(AktivitetspliktOppgaveToggles.UNNTAK_MED_FRIST, false)) {
logger.info("Starter jobb for å lage oppfølgingsoppgaver for unntak med frist")
newSingleThreadContext("aktivitetspliktOppgaveUnntakUtloeperJob").use { ctx ->
Runtime.getRuntime().addShutdownHook(Thread { ctx.close() })
runBlocking(ctx) {
opprettOppfoelgingsOppgaveForAktivitetspliktUnntakUtloeper()
}
}
} else {
logger.info("Jobb for å lage oppfølgingsoppgaver for unntak med frist er skrudd av")
}
}

private suspend fun opprettOppfoelgingsOppgaveForAktivitetspliktUnntakUtloeper() {
val tomDato = LocalDate.now().plusMonths(2)
val sakIds = aktivitetspliktDao.finnSakerKlarForOppfoelgingsoppgaveVarigUnntakUtloeper(tomDato)
logger.info("Fant ${sakIds.size} saker som skal sjekkes for oppfølgingsoppgaver unntak utløper")

sakIds.forEach { sakId ->
if (!vedtakKlient
.sakHarLopendeVedtakPaaDato(
sakId,
tomDato,
HardkodaSystembruker.oppgave,
).erLoepende
) {
logger.info("Lager ikke oppfølgingsoppgave for sak $sakId grunnen ikke løpende på dato $tomDato")
return@forEach
}

val sak = aktivitetspliktService.hentVurderingForSak(sakId)
val oppgaveReferanser =
oppgaveService
.hentOppgaverForSak(sakId)
.filter { it.type == OppgaveType.OPPFOELGING }
.map { it.referanse }

sak.unntak
.filter { it.tom != null && it.tom <= tomDato }
.forEach { unntak ->
val harOppave = oppgaveReferanser.any { unntak.id.toString() == it }
if (harOppave) {
return@forEach
} else {
logger.info("Oppretter oppfølgingsoppgave for sak $sakId for unntak ${unntak.id}")
oppgaveService.opprettOppgave(
referanse = unntak.id.toString(),
sakId = sakId,
kilde = OppgaveKilde.SAKSBEHANDLER,
type = OppgaveType.OPPFOELGING,
merknad = "Unntak ${unntak.unntak.lesbartNavn} utløper",
frist =
unntak.tom!!
.minusMonths(1)
.atStartOfDay()
.toTidspunkt(),
saksbehandler = null,
gruppeId = null,
)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import no.nav.etterlatte.behandling.generellbehandling.GenerellBehandlingDao
import no.nav.etterlatte.behandling.generellbehandling.GenerellBehandlingService
import no.nav.etterlatte.behandling.hendelse.HendelseDao
import no.nav.etterlatte.behandling.job.SaksbehandlerJobService
import no.nav.etterlatte.behandling.jobs.AktivitetspliktOppgaveUnntakUtloeperJob
import no.nav.etterlatte.behandling.jobs.AktivitetspliktOppgaveUnntakUtloeperJobService
import no.nav.etterlatte.behandling.jobs.DoedsmeldingJob
import no.nav.etterlatte.behandling.jobs.DoedsmeldingReminderJob
import no.nav.etterlatte.behandling.jobs.SaksbehandlerJob
Expand Down Expand Up @@ -565,6 +567,15 @@ internal class ApplicationContext(

val saksbehandlerJobService = SaksbehandlerJobService(saksbehandlerInfoDao, navAnsattKlient, axsysKlient)

val aktivitetspliktOppgaveUnntakUtloeperJobService =
AktivitetspliktOppgaveUnntakUtloeperJobService(
aktivitetspliktDao,
aktivitetspliktService,
oppgaveService,
vedtakKlient,
featureToggleService,
)

val gosysOppgaveService =
GosysOppgaveServiceImpl(
gosysOppgaveKlient,
Expand Down Expand Up @@ -634,6 +645,15 @@ internal class ApplicationContext(
)
}

val aktivitetspliktOppgaveUnntakUtloeperJob: AktivitetspliktOppgaveUnntakUtloeperJob by lazy {
AktivitetspliktOppgaveUnntakUtloeperJob(
aktivitetspliktOppgaveUnntakUtloeperJobService,
{ leaderElectionKlient.isLeader() },
initialDelay = if (isProd()) Duration.of(3, ChronoUnit.MINUTES).toMillis() else Duration.of(20, ChronoUnit.MINUTES).toMillis(),
interval = if (isProd()) Duration.of(1, ChronoUnit.DAYS) else Duration.of(1, ChronoUnit.HOURS),
)
}

val doedsmeldingerJob: DoedsmeldingJob by lazy {
DoedsmeldingJob(
doedshendelseJobService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -703,6 +703,7 @@ class OppgaveService(
OppgaveType.AARLIG_INNTEKTSJUSTERING,
OppgaveType.INNTEKTSOPPLYSNING,
OppgaveType.MANUELL_UTSENDING_BREV,
OppgaveType.OPPFOELGING,
-> {
logger.info(
"Tilbakestiller ikke oppgave av type ${it.type} " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import no.nav.etterlatte.libs.common.OpeningHours
import no.nav.etterlatte.libs.common.TimerJob
import no.nav.etterlatte.libs.tidshendelser.JobbType
import no.nav.etterlatte.tidshendelser.hendelser.HendelseDao
import no.nav.etterlatte.tidshendelser.hendelser.HendelserJobb
import org.slf4j.LoggerFactory
import java.time.Duration
import java.time.YearMonth
Expand All @@ -30,7 +29,7 @@ class JobbSchedulerTask(
loggerInfo = LoggerInfo(logger = logger),
openingHours = openingHours,
) {
jobbScheduler.poll()
jobbScheduler.scheduleMaanedligeJobber()
}
}
}
Expand All @@ -40,28 +39,24 @@ class JobbScheduler(
) {
private val logger = LoggerFactory.getLogger(JobbPoller::class.java)

fun poll() {
fun scheduleMaanedligeJobber() {
val nesteMaaned = YearMonth.now().plusMonths(1)
logger.info("Sjekker for jobber å legge til for måned: $nesteMaaned")

val planlagteJobberNesteMnd = hendelseDao.finnJobberMedKjoeringForMaaned(nesteMaaned)

// Ta bort de periodiske jobbene som allerede er lagt inn for neste mnd
filtrerBortPlanlagteJobber(planlagteJobberNesteMnd).forEach { fasteJobber ->
// For de periodiske jobbene som mangler, opprett jobb for neste mnd
hendelseDao.opprettJobb(fasteJobber, nesteMaaned)
}
}

private fun filtrerBortPlanlagteJobber(kjoreringNesteMaaned: List<HendelserJobb>) =
PeriodiskeJobber.entries.filter { fastJobb ->
kjoreringNesteMaaned.none { kjoringNesteMaaned ->
kjoringNesteMaaned.type ==
fastJobb.jobbType
PeriodiskeMaanedligeJobber.entries
// filtrere bort jobber som allerede er planlagt for neste måned
.filter { periodiskJobb ->
planlagteJobberNesteMnd.none { kjoering -> kjoering.type == periodiskJobb.jobbType }
}
}
// opprett jobb for neste måned
.forEach { periodiskJobb ->
hendelseDao.opprettMaanedligJobb(periodiskJobb, nesteMaaned)
}
}

enum class PeriodiskeJobber(
enum class PeriodiskeMaanedligeJobber(
val jobbType: JobbType,
val dagIMaaned: Int,
// Merk at justering av behandlingMaaned kan medføre uønsket oppførsel (f.eks. har løpende ytelse sjekker feil måned)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import no.nav.etterlatte.libs.database.transaction
import no.nav.etterlatte.libs.tidshendelser.JobbType
import no.nav.etterlatte.tidshendelser.JobbScheduler
import org.slf4j.LoggerFactory
import java.time.LocalDate
import java.time.YearMonth
import java.util.UUID
import javax.sql.DataSource
Expand All @@ -33,17 +34,22 @@ class HendelseDao(
?: throw NoSuchElementException("Fant ikke jobb med id $id")
}

fun finnAktuellJobb(): List<HendelserJobb> =
// Henter jobber for kjoeredato, hvis ikke spesifisert brukes CURRENT_DATE
// COALESCE = returns first non-NULL value
fun finnAktuellJobb(kjoeredato: LocalDate? = null): List<HendelserJobb> =
datasource.transaction { tx ->
queryOf(
"""
SELECT * FROM jobb
WHERE status = :status
AND kjoeredato = CURRENT_DATE
AND kjoeredato = COALESCE(:kjoeredato, CURRENT_DATE)
ORDER BY id asc
LIMIT 1
""".trimIndent(),
mapOf("status" to JobbStatus.NY.name),
mapOf(
"status" to JobbStatus.NY.name,
"kjoeredato" to kjoeredato,
),
).let { query -> tx.run(query.map { row -> row.toHendelserJobb() }.asList) }
}

Expand Down Expand Up @@ -131,8 +137,8 @@ class HendelseDao(
}
}

fun opprettJobb(
jobb: JobbScheduler.PeriodiskeJobber,
fun opprettMaanedligJobb(
jobb: JobbScheduler.PeriodiskeMaanedligeJobber,
maaned: YearMonth,
) {
datasource.transaction { tx ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package no.nav.etterlatte.tidshendelser
import io.kotest.matchers.collections.shouldHaveSize
import io.mockk.clearAllMocks
import no.nav.etterlatte.libs.tidshendelser.JobbType
import no.nav.etterlatte.tidshendelser.JobbScheduler.PeriodiskeJobber
import no.nav.etterlatte.tidshendelser.JobbScheduler.PeriodiskeMaanedligeJobber
import no.nav.etterlatte.tidshendelser.hendelser.HendelseDao
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
Expand Down Expand Up @@ -40,8 +40,8 @@ class JobberIntegrationTestScheduler(

@Test
fun `skal lage jobber om de ikke er laget fra før`() {
jobbScheduler.poll()
hendelseDao.finnJobberMedKjoeringForMaaned(nesteMaaned) shouldHaveSize PeriodiskeJobber.entries.size
jobbScheduler.scheduleMaanedligeJobber()
hendelseDao.finnJobberMedKjoeringForMaaned(nesteMaaned) shouldHaveSize PeriodiskeMaanedligeJobber.entries.size
}

@Test
Expand All @@ -51,9 +51,9 @@ class JobberIntegrationTestScheduler(
nesteMaaned,
nesteMaaned.atDay(5),
)
jobbScheduler.poll()
jobbScheduler.scheduleMaanedligeJobber()

hendelseDao.finnJobberMedKjoeringForMaaned(nesteMaaned) shouldHaveSize PeriodiskeJobber.entries.size
hendelseDao.finnJobberMedKjoeringForMaaned(nesteMaaned) shouldHaveSize PeriodiskeMaanedligeJobber.entries.size
}

@Test
Expand All @@ -63,8 +63,8 @@ class JobberIntegrationTestScheduler(
nesteMaaned,
nesteMaaned.atDay(5),
)
jobbScheduler.poll()
jobbScheduler.scheduleMaanedligeJobber()

hendelseDao.finnJobberMedKjoeringForMaaned(nesteMaaned) shouldHaveSize PeriodiskeJobber.entries.size + 1
hendelseDao.finnJobberMedKjoeringForMaaned(nesteMaaned) shouldHaveSize PeriodiskeMaanedligeJobber.entries.size + 1
}
}
Loading

0 comments on commit 0e93431

Please sign in to comment.