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

NAV-24387: Forvalter-endepunkt for å kunne korrigere andeler i fagsaker med avvik. #5168

Merged
Merged
Show file tree
Hide file tree
Changes from 13 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 @@ -28,6 +28,7 @@ enum class FeatureToggle(

// NAV-24387
BRUK_UTBETALINGSTIDSLINJER_VED_GENERERING_AV_PERIODER_TIL_AVSTEMMING("familie-ba-sak.bruk-utbetalingstidslinjer-ved-generering-av-perioder-til-avstemming"),
SKAL_FINNE_OG_PATCHE_ANDELER_I_FAGAKER_MED_AVVIK("familie-ba-sak.skal-finne-og-patche-andeler-i-fagsaker-med-avvik"),

// satsendring
// Oppretter satsendring-tasker for de som ikke har fått ny task
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package no.nav.familie.ba.sak.integrasjoner.økonomi.utbetalingsoppdrag

import no.nav.familie.ba.sak.common.ClockProvider
import no.nav.familie.ba.sak.common.Feil
import no.nav.familie.ba.sak.common.secureLogger
import no.nav.familie.ba.sak.common.toYearMonth
import no.nav.familie.ba.sak.internal.AndelTilkjentYtelseKorreksjon
import no.nav.familie.ba.sak.kjerne.beregning.domene.AndelTilkjentYtelse
import no.nav.familie.ba.sak.kjerne.beregning.domene.PatchetAndelTilkjentYtelseRepository
import no.nav.familie.ba.sak.kjerne.beregning.domene.TilkjentYtelse
import no.nav.familie.ba.sak.kjerne.beregning.domene.TilkjentYtelseRepository
import no.nav.familie.ba.sak.kjerne.beregning.domene.tilAndelerTilkjentYtelseMedEndreteUtbetalinger
import no.nav.familie.ba.sak.kjerne.beregning.domene.tilPatchetAndelTilkjentYtelse
import no.nav.familie.ba.sak.kjerne.endretutbetaling.EndretUtbetalingAndelHentOgPersisterService
import no.nav.familie.ba.sak.kjerne.endretutbetaling.domene.EndretUtbetalingAndel
import no.nav.familie.ba.sak.kjerne.endretutbetaling.domene.førerTilOpphør
Expand All @@ -15,13 +19,15 @@ import no.nav.familie.felles.utbetalingsgenerator.domain.BeregnetUtbetalingsoppd
import no.nav.familie.felles.utbetalingsgenerator.domain.Utbetalingsoppdrag
import no.nav.familie.kontrakter.felles.objectMapper
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
import java.time.YearMonth

@Service
class OppdaterTilkjentYtelseService(
private val endretUtbetalingAndelHentOgPersisterService: EndretUtbetalingAndelHentOgPersisterService,
private val tilkjentYtelseRepository: TilkjentYtelseRepository,
private val patchetAndelTilkjentYtelseRepository: PatchetAndelTilkjentYtelseRepository,
private val clockProvider: ClockProvider,
) {
fun oppdaterTilkjentYtelseMedUtbetalingsoppdrag(
Expand All @@ -46,6 +52,23 @@ class OppdaterTilkjentYtelseService(
tilkjentYtelseRepository.save(tilkjentYtelse)
}

@Transactional
fun oppdaterTilkjentYtelseMedKorrigerteAndeler(
tilkjentYtelse: TilkjentYtelse,
andelTilkjentYtelseKorreksjoner: List<AndelTilkjentYtelseKorreksjon>,
) {
val andelerSomSkalSlettes = andelTilkjentYtelseKorreksjoner.map { it.andelMedFeil }
val andelerSomSkalOpprettes = andelTilkjentYtelseKorreksjoner.map { it.korrigertAndel }

val andelerSomSkalSlettesGruppertPåId = andelerSomSkalSlettes.groupBy { it.id }
if (andelerSomSkalSlettesGruppertPåId.any { it.value.size > 1 }) throw Feil("Den samme andelen forekommer flere ganger blant andelene som er markert for sletting. Dette betyr at det finnes en splitt i utbetalingsoppdragene oversendt til Oppdrag som ikke eksisterer i andelene.")

patchetAndelTilkjentYtelseRepository.saveAll(andelerSomSkalSlettes.map { it.tilPatchetAndelTilkjentYtelse() })

tilkjentYtelse.andelerTilkjentYtelse.removeAll(andelerSomSkalSlettes.toSet())
tilkjentYtelse.andelerTilkjentYtelse.addAll(andelerSomSkalOpprettes.toSet())
}

private fun oppdaterTilkjentYtelseMedUtbetalingsoppdrag(
tilkjentYtelse: TilkjentYtelse,
utbetalingsoppdrag: Utbetalingsoppdrag,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse
import io.swagger.v3.oas.annotations.responses.ApiResponses
import jakarta.validation.Valid
import no.nav.familie.ba.sak.common.Feil
import no.nav.familie.ba.sak.common.FunksjonellFeil
import no.nav.familie.ba.sak.common.secureLogger
import no.nav.familie.ba.sak.config.AuditLoggerEvent
import no.nav.familie.ba.sak.config.BehandlerRolle
Expand Down Expand Up @@ -509,4 +510,37 @@ class ForvalterController(

return ResponseEntity.ok(utbetalingsTidslinjeService.genererUtbetalingstidslinjerForFagsak(fagsakId).map { it.tilUtbetalingsperioder() })
}

@PostMapping("/finn-og-patch-andeler-tilkjent-ytelse-i-fagsaker-med-avvik")
@Operation(
summary = "Finner og patcher andeler tilkjent ytelse i fagsaker med avvik i konsistensavstemming",
description =
"Bruker Utbetalingtidslinjer til å sammenligne andelerTilkjentYtelse med faktiske utbetalingsperioder oversendt til Oppdrag." +
"Finner vi forskjeller mellom en andel og en utbetalingsperiode slettes den originale andelen og erstattes av en korrigert andel.",
)
fun finnOgPatchAndelerTilkjentYtelseIFagsakerMedAvvik(
@RequestBody finnOgPatchAndelerRequestDto: FinnOgPatchAndelerRequestDto,
): ResponseEntity<List<Pair<Long, List<AndelTilkjentYtelseKorreksjonDto>?>>> {
tilgangService.verifiserHarTilgangTilHandling(
minimumBehandlerRolle = BehandlerRolle.FORVALTER,
handling = "Finne og patche andeler tilkjent ytelse i fagsaker med avvik i konsistensavstemming",
)
if (!unleashNextMedContextService.isEnabled(FeatureToggle.SKAL_FINNE_OG_PATCHE_ANDELER_I_FAGAKER_MED_AVVIK, false)) {
throw FunksjonellFeil("Kan ikke finne og patche andeler. Toggelen ${FeatureToggle.SKAL_FINNE_OG_PATCHE_ANDELER_I_FAGAKER_MED_AVVIK} er skrudd av")
}

return ResponseEntity.ok(
forvalterService.finnOgPatchAndelerTilkjentYtelseIFagsakerMedAvvik(
fagsaker = finnOgPatchAndelerRequestDto.fagsaker,
korrigerAndelerFraOgMedDato = finnOgPatchAndelerRequestDto.korrigerAndelerFraOgMedDato,
dryRun = finnOgPatchAndelerRequestDto.dryRun,
),
)
}
}

data class FinnOgPatchAndelerRequestDto(
val fagsaker: Set<Long>,
val korrigerAndelerFraOgMedDato: LocalDate = LocalDate.of(2025, 2, 1),
val dryRun: Boolean = true,
)
126 changes: 126 additions & 0 deletions src/main/kotlin/no/nav/familie/ba/sak/internal/ForvalterService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,47 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import no.nav.familie.ba.sak.common.Feil
import no.nav.familie.ba.sak.common.UtbetalingsikkerhetFeil
import no.nav.familie.ba.sak.common.førsteDagIInneværendeMåned
import no.nav.familie.ba.sak.common.secureLogger
import no.nav.familie.ba.sak.common.sisteDagIInneværendeMåned
import no.nav.familie.ba.sak.common.toYearMonth
import no.nav.familie.ba.sak.integrasjoner.økonomi.UtbetalingsTidslinjeService
import no.nav.familie.ba.sak.integrasjoner.økonomi.Utbetalingstidslinje
import no.nav.familie.ba.sak.integrasjoner.økonomi.utbetalingsoppdrag.OppdaterTilkjentYtelseService
import no.nav.familie.ba.sak.integrasjoner.økonomi.ØkonomiService
import no.nav.familie.ba.sak.kjerne.arbeidsfordeling.ArbeidsfordelingService
import no.nav.familie.ba.sak.kjerne.behandling.BehandlingHentOgPersisterService
import no.nav.familie.ba.sak.kjerne.behandling.domene.BehandlingRepository
import no.nav.familie.ba.sak.kjerne.beregning.BeregningService
import no.nav.familie.ba.sak.kjerne.beregning.TilkjentYtelseValideringService
import no.nav.familie.ba.sak.kjerne.beregning.domene.AndelTilkjentYtelse
import no.nav.familie.ba.sak.kjerne.beregning.domene.TilkjentYtelseRepository
import no.nav.familie.ba.sak.kjerne.beregning.domene.YtelseType
import no.nav.familie.ba.sak.kjerne.fagsak.FagsakRepository
import no.nav.familie.ba.sak.kjerne.grunnlag.personopplysninger.PersongrunnlagService
import no.nav.familie.ba.sak.kjerne.personident.Aktør
import no.nav.familie.ba.sak.kjerne.tidslinje.transformasjon.beskjærFraOgMed
import no.nav.familie.ba.sak.kjerne.vedtak.VedtakService
import no.nav.familie.ba.sak.kjerne.vilkårsvurdering.VilkårsvurderingService
import no.nav.familie.ba.sak.kjerne.vilkårsvurdering.domene.PersonResultat
import no.nav.familie.ba.sak.kjerne.vilkårsvurdering.domene.Vilkår
import no.nav.familie.ba.sak.kjerne.vilkårsvurdering.domene.Vilkår.UNDER_18_ÅR
import no.nav.familie.ba.sak.kjerne.vilkårsvurdering.domene.Vilkårsvurdering
import no.nav.familie.log.mdc.MDCConstants
import no.nav.familie.tidslinje.Periode
import no.nav.familie.tidslinje.Tidslinje
import no.nav.familie.tidslinje.tilTidslinje
import no.nav.familie.tidslinje.utvidelser.filtrerIkkeNull
import no.nav.familie.tidslinje.utvidelser.kombinerMed
import no.nav.familie.tidslinje.utvidelser.tilPerioderIkkeNull
import no.nav.familie.tidslinje.verdier
import org.slf4j.LoggerFactory
import org.slf4j.MDC
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.LocalDate
import java.time.YearMonth

@Service
class ForvalterService(
Expand All @@ -46,6 +64,9 @@ class ForvalterService(
private val arbeidsfordelingService: ArbeidsfordelingService,
private val vilkårsvurderingService: VilkårsvurderingService,
private val persongrunnlagService: PersongrunnlagService,
private val utbetalingsTidslinjeService: UtbetalingsTidslinjeService,
private val tilkjentYtelseRepository: TilkjentYtelseRepository,
private val oppdaterTilkjentYtelseService: OppdaterTilkjentYtelseService,
) {
private val logger = LoggerFactory.getLogger(ForvalterService::class.java)

Expand Down Expand Up @@ -210,9 +231,114 @@ class ForvalterService(
throw Feil("Det finnes flere vilkårresultater som begynner før fødselsdato til person: $this")
}
}

@Transactional
fun finnOgPatchAndelerTilkjentYtelseIFagsakerMedAvvik(
fagsaker: Set<Long>,
korrigerAndelerFraOgMedDato: LocalDate,
dryRun: Boolean = true,
): List<Pair<Long, List<AndelTilkjentYtelseKorreksjonDto>?>> {
return fagsaker.map { fagsakId ->
val sisteIverksatteBehandling = behandlingHentOgPersisterService.hentSisteBehandlingSomErIverksatt(fagsakId = fagsakId)
if (sisteIverksatteBehandling != null) {
val tilkjentYtelse = tilkjentYtelseRepository.findByBehandling(behandlingId = sisteIverksatteBehandling.id)
val andelerTilkjentYtelse = tilkjentYtelse.andelerTilkjentYtelse.groupBy { Pair(it.aktør, it.type) }
val utbetalingstidslinjer = utbetalingsTidslinjeService.genererUtbetalingstidslinjerForFagsak(fagsakId = fagsakId)
val andelTilkjentYtelseKorreksjoner: List<AndelTilkjentYtelseKorreksjon> =
utbetalingstidslinjer.flatMap { utbetalingstidslinje ->
val andeltidslinjeForUtbetalingstidslinje = finnAndelerTilkjentYtelseForUtbetalingstidslinje(utbetalingstidslinje = utbetalingstidslinje, andelerTilkjentYtelsePerAktørOgType = andelerTilkjentYtelse)
utbetalingstidslinje.tidslinje
// Ser kun på andeler som løper fra og med korrigerAndelerFraOgMedDato. Tanken er at vi ikke ønsker å korrigere bakover i tid.
.beskjærFraOgMed(korrigerAndelerFraOgMedDato)
.kombinerMed(andeltidslinjeForUtbetalingstidslinje) { utbetalingsperiode, andelTilkjentYtelse ->
// Dersom verken utbetalingsperiode eller andelTilkjentYtelse er null betyr det at de overlapper.
// Dersom de har ulike verdier for enten periodeId, forrigePeriodeId eller kildeBehandlingId oppretter vi en AndelTilkjentYtelseKorreksjon
// Korreksjonen inneholder andelen med feil samt en korrigert versjon av andelen.
if (utbetalingsperiode != null && andelTilkjentYtelse != null) {
if (utbetalingsperiode.periodeId != andelTilkjentYtelse.periodeOffset || utbetalingsperiode.forrigePeriodeId != andelTilkjentYtelse.forrigePeriodeOffset || utbetalingsperiode.behandlingId != andelTilkjentYtelse.kildeBehandlingId) {
return@kombinerMed AndelTilkjentYtelseKorreksjon(andelTilkjentYtelse, andelTilkjentYtelse.copy(id = 0, periodeOffset = utbetalingsperiode.periodeId, forrigePeriodeOffset = utbetalingsperiode.forrigePeriodeId, kildeBehandlingId = utbetalingsperiode.behandlingId))
}
}
return@kombinerMed null
}.filtrerIkkeNull()
.tilPerioderIkkeNull()
.verdier()
}

if (!dryRun) {
// Sletter andeler med feil og oppretter korrigerte andeler.
// Slettede andeler lagres i ny tabell PatchetAndelTilkjentYtelse slik at vi har mulighet til å finne ut hvordan de så ut før endringen.
oppdaterTilkjentYtelseService.oppdaterTilkjentYtelseMedKorrigerteAndeler(
tilkjentYtelse = tilkjentYtelse,
andelTilkjentYtelseKorreksjoner = andelTilkjentYtelseKorreksjoner,
)
}
return@map Pair(fagsakId, andelTilkjentYtelseKorreksjoner.tilAndelerTilkjentYtelseKorreksjonerDto())
}
return@map Pair(fagsakId, null)
}
}

private fun finnAndelerTilkjentYtelseForUtbetalingstidslinje(
utbetalingstidslinje: Utbetalingstidslinje,
andelerTilkjentYtelsePerAktørOgType: Map<Pair<Aktør, YtelseType>, List<AndelTilkjentYtelse>>,
): Tidslinje<AndelTilkjentYtelse> =
andelerTilkjentYtelsePerAktørOgType.entries
.single { (_, andeler) ->

val førsteOffset =
andeler
.firstOrNull { it.erAndelSomSkalSendesTilOppdrag() && it.periodeOffset != null }
?.periodeOffset
if (førsteOffset != null) {
return@single utbetalingstidslinje.erTidslinjeForPeriodeId(førsteOffset)
}
return@single false
}.value
.map { Periode(verdi = it, fom = it.stønadFom.førsteDagIInneværendeMåned(), tom = it.stønadTom.sisteDagIInneværendeMåned()) }
.tilTidslinje()
}

interface FagsakMedFlereMigreringer {
val fagsakId: Long
val fødselsnummer: String
}

data class AndelTilkjentYtelseKorreksjon(
val andelMedFeil: AndelTilkjentYtelse,
val korrigertAndel: AndelTilkjentYtelse,
)

fun List<AndelTilkjentYtelseKorreksjon>.tilAndelerTilkjentYtelseKorreksjonerDto() =
this.map {
AndelTilkjentYtelseKorreksjonDto(
andelMedFeil = it.andelMedFeil.tilAndelTilkjentYtelseDto(),
korrigertAndel = it.korrigertAndel.tilAndelTilkjentYtelseDto(),
)
}

fun AndelTilkjentYtelse.tilAndelTilkjentYtelseDto() =
AndelTilkjentYtelseDto(
id = this.id,
stønadFom = this.stønadFom,
stønadTom = this.stønadTom,
beløp = this.kalkulertUtbetalingsbeløp,
periodeId = this.periodeOffset,
forrigePeriodeId = this.forrigePeriodeOffset,
kildeBehandlingId = this.kildeBehandlingId,
)

data class AndelTilkjentYtelseKorreksjonDto(
val andelMedFeil: AndelTilkjentYtelseDto,
val korrigertAndel: AndelTilkjentYtelseDto,
)

data class AndelTilkjentYtelseDto(
val id: Long,
val stønadFom: YearMonth,
val stønadTom: YearMonth,
val beløp: Int,
val periodeId: Long?,
val forrigePeriodeId: Long?,
val kildeBehandlingId: Long?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package no.nav.familie.ba.sak.kjerne.beregning.domene

import jakarta.persistence.Column
import jakarta.persistence.Convert
import jakarta.persistence.Entity
import jakarta.persistence.EntityListeners
import jakarta.persistence.EnumType
import jakarta.persistence.Enumerated
import jakarta.persistence.Id
import jakarta.persistence.Table
import no.nav.familie.ba.sak.common.BaseEntitet
import no.nav.familie.ba.sak.common.YearMonthConverter
import no.nav.familie.ba.sak.sikkerhet.RollestyringMotDatabase
import java.math.BigDecimal
import java.time.YearMonth

@EntityListeners(RollestyringMotDatabase::class)
@Entity(name = "PatchetAndelTilkjentYtelse")
@Table(name = "PATCHET_ANDEL_TILKJENT_YTELSE")
data class PatchetAndelTilkjentYtelse(
@Id
@Column(name = "id")
val id: Long,
@Column(name = "behandling_id", nullable = false, updatable = false)
val behandlingId: Long,
@Column(name = "tilkjent_ytelse_id", nullable = false, updatable = false)
var tilkjentYtelseId: Long,
@Column(name = "aktoer_id", nullable = false, updatable = false)
val aktørId: String,
@Column(name = "kalkulert_utbetalingsbelop", nullable = false)
val kalkulertUtbetalingsbeløp: Int,
@Column(name = "stonad_fom", nullable = false, columnDefinition = "DATE")
@Convert(converter = YearMonthConverter::class)
val stønadFom: YearMonth,
@Column(name = "stonad_tom", nullable = false, columnDefinition = "DATE")
@Convert(converter = YearMonthConverter::class)
val stønadTom: YearMonth,
@Enumerated(EnumType.STRING)
@Column(name = "type", nullable = false)
val type: YtelseType,
@Column(name = "sats", nullable = false)
val sats: Int,
@Column(name = "prosent", nullable = false)
val prosent: BigDecimal,
@Column(name = "kilde_behandling_id")
var kildeBehandlingId: Long? = null,
@Column(name = "periode_offset")
var periodeOffset: Long? = null,
@Column(name = "forrige_periode_offset")
var forrigePeriodeOffset: Long? = null,
@Column(name = "nasjonalt_periodebelop")
val nasjonaltPeriodebeløp: Int?,
@Column(name = "differanseberegnet_periodebelop")
val differanseberegnetPeriodebeløp: Int? = null,
) : BaseEntitet()
Copy link
Contributor

Choose a reason for hiding this comment

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

Gir det mening å ha baseentitet her? Feltene i baseentitet vil settes til din bruker og når raden i PATCHET_ANDEL_TILKJENT_YTELSE opprettes, men egentlig er man vel interessert i det som faktisk var på aty'en.


fun AndelTilkjentYtelse.tilPatchetAndelTilkjentYtelse(): PatchetAndelTilkjentYtelse =
PatchetAndelTilkjentYtelse(
id = this.id,
behandlingId = this.behandlingId,
tilkjentYtelseId = this.tilkjentYtelse.id,
aktørId = this.aktør.aktørId,
kalkulertUtbetalingsbeløp = this.kalkulertUtbetalingsbeløp,
stønadFom = this.stønadFom,
stønadTom = this.stønadTom,
type = this.type,
sats = this.sats,
prosent = this.prosent,
kildeBehandlingId = this.kildeBehandlingId,
periodeOffset = this.periodeOffset,
forrigePeriodeOffset = this.forrigePeriodeOffset,
nasjonaltPeriodebeløp = this.nasjonaltPeriodebeløp,
differanseberegnetPeriodebeløp = this.differanseberegnetPeriodebeløp,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package no.nav.familie.ba.sak.kjerne.beregning.domene

import org.springframework.data.jpa.repository.JpaRepository

interface PatchetAndelTilkjentYtelseRepository : JpaRepository<PatchetAndelTilkjentYtelse, Long>
Loading