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

Fix: PPN kan bruke 100% av kvoten ved gradert uttak #923

Merged
merged 7 commits into from
Mar 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 @@ -6,6 +6,7 @@ enum class Årsak(val oppfylt: Boolean) {
AVKORTET_MOT_INNTEKT(true),
OVERSTYRT_UTTAKSGRAD(true),
AVKORTET_MOT_SØKERS_ØNSKE(true),
AVKORTET_MOT_KVOTE(oppfylt=true),
@Deprecated("Bruk OPPFYLT_PGA_BARNETS_DØDSFALL_6_UKER eller OPPFYLT_PGA_BARNETS_DØDSFALL_12_UKER i stedet for OPPFYLT_PGA_BARNETS_DØDSFALL")
OPPFYLT_PGA_BARNETS_DØDSFALL(true),
OPPFYLT_PGA_BARNETS_DØDSFALL_6_UKER(true),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package no.nav.pleiepengerbarn.uttak.regler.delregler

import no.nav.pleiepengerbarn.uttak.kontrakter.*
import no.nav.pleiepengerbarn.uttak.regler.FeatureToggle
import no.nav.pleiepengerbarn.uttak.regler.HUNDRE_PROSENT
import no.nav.pleiepengerbarn.uttak.regler.domene.RegelGrunnlag
import no.nav.pleiepengerbarn.uttak.regler.kontrakter_ext.overlapperDelvis
import no.nav.pleiepengerbarn.uttak.regler.kontrakter_ext.virkedager
import org.slf4j.LoggerFactory
import java.math.BigDecimal
import java.math.RoundingMode
import java.time.DayOfWeek
import java.time.LocalDate
import java.util.UUID
import java.util.*

internal class MaxAntallDagerRegel : UttaksplanRegel {

Expand All @@ -25,8 +25,7 @@ internal class MaxAntallDagerRegel : UttaksplanRegel {
if (grunnlag.ytelseType != YtelseType.PLS) {
return uttaksplan
}
val maxDager =
KVOTER[grunnlag.ytelseType] ?: throw IllegalArgumentException("Ulovlig ytelsestype ${grunnlag.ytelseType}")
val maxDager = KVOTER[grunnlag.ytelseType] ?: throw IllegalArgumentException("Ulovlig ytelsestype ${grunnlag.ytelseType}")

val (forBrukteDagerHittil, maxDatoHittil) = grunnlag.finnForbrukteDagerHittil()

Expand All @@ -36,24 +35,42 @@ internal class MaxAntallDagerRegel : UttaksplanRegel {

uttaksplan.perioder.forEach { (periode, info) ->
if (info.utfall == Utfall.OPPFYLT) {
val forbrukteDagerDennePerioen = BigDecimal(periode.virkedager()) * (info.uttaksgrad.divide(HUNDRE_PROSENT, 2, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP))
val uttaksgrad = info.uttaksgrad.divide(HUNDRE_PROSENT, 2, RoundingMode.HALF_UP)
val forbrukteDagerDennePerioen = BigDecimal(periode.virkedager()) * uttaksgrad

if (rest <= BigDecimal.ZERO) {
// Hvis ingenting igjen på kvoten så må undersøke om det fremdeles kan innvilges
// ingenting igjen på kvoten
nyePerioder[periode] = info.settIkkeoppfylt()
} else if (forbrukteDagerDennePerioen <= rest) {
// Hvis det er nok dager igjen, så settes hele periode til oppfylt
// nok dager igjen, setter hele perioden til oppfylt
nyePerioder[periode] = info
rest -= forbrukteDagerDennePerioen
} else {
// Bare delvis nok dager igjen, så deler derfor opp perioden i en oppfylt og en ikke oppfylt periode
val restHeleDager = rest.setScale(0, RoundingMode.UP)
val restHeleDagerMedEventuellHelg = if (restHeleDager > BigDecimal(5)) ((restHeleDager.divide(BigDecimal(5), 2, RoundingMode.HALF_UP)) * BigDecimal(2)) + restHeleDager - BigDecimal(2) else restHeleDager
nyePerioder[LukketPeriode(periode.fom, periode.fom.plusDays((restHeleDagerMedEventuellHelg - BigDecimal.ONE).toLong()))] = info
if (erDetFlereDagerIgjenÅVurdere(periode, restHeleDagerMedEventuellHelg)) {
nyePerioder[LukketPeriode(periode.fom.plusDays(restHeleDagerMedEventuellHelg.toLong()), periode.tom)] = info.settIkkeoppfylt()
// delvis nok dager igjen.
val restHeleVirkedager = rest.divide(uttaksgrad, 0, RoundingMode.DOWN).toInt() // ingen #div/0, treffer kode over om det er 0 uttaksgrad
rest -= uttaksgrad * BigDecimal(restHeleVirkedager)

val fårDagerHeltOppfylt = restHeleVirkedager > 0
val fårDagMedDelvisOppfylt = rest > BigDecimal.ZERO

var nestePeriodeFom = periode.fom;
if (fårDagerHeltOppfylt) {
val tomInnvilget = if (fårDagMedDelvisOppfylt)
plussVirkedager(periode.fom, restHeleVirkedager).minusDays(1) //for å ta med helg når påfølgende mandag innvilges delvis
else
plussVirkedager(periode.fom, restHeleVirkedager - 1)
nyePerioder[LukketPeriode(periode.fom, tomInnvilget)] = info
nestePeriodeFom = tomInnvilget.plusDays(1)
}
if (fårDagMedDelvisOppfylt) {
val restIProsenter = BigDecimal(100) * rest
nyePerioder[LukketPeriode(nestePeriodeFom, nestePeriodeFom)] = info.settDelvisOppfyltAvkortetMotKvote(restIProsenter)
rest = BigDecimal.ZERO
nestePeriodeFom = nestePeriodeFom.plusDays(1)
}
if (nestePeriodeFom <= periode.tom) {
nyePerioder[LukketPeriode(nestePeriodeFom, periode.tom)] = info.settIkkeoppfylt()
}
rest = BigDecimal.ZERO
}
} else {
// Gjør ingenting med perioder som ikke er oppfylt
Expand Down Expand Up @@ -86,7 +103,17 @@ internal class MaxAntallDagerRegel : UttaksplanRegel {
return uttaksplan.copy(perioder = nyePerioder, kvoteInfo = kvoteInfo)
}

private fun erDetFlereDagerIgjenÅVurdere(periode: LukketPeriode, restHeleDagerMedEventuellHelg: BigDecimal) = !periode.tom.isBefore(periode.fom.plusDays(restHeleDagerMedEventuellHelg.toLong()))
private fun plussVirkedager(inputDato: LocalDate, antallVirkedager: Int): LocalDate {
var restVirkedager = antallVirkedager
var dato = inputDato
while (restVirkedager > 0) {
dato = dato.plusDays(1);
if (dato.dayOfWeek !in listOf(DayOfWeek.SATURDAY, DayOfWeek.SUNDAY)) {
restVirkedager--;
}
}
return dato
}

private fun skalKunSetteMaxDatoHvisKvotenErbruktOpp(
forBrukteDagerHittil: BigDecimal,
Expand Down Expand Up @@ -120,6 +147,22 @@ private fun UttaksperiodeInfo.settIkkeoppfylt(): UttaksperiodeInfo {
)
}

private fun UttaksperiodeInfo.settDelvisOppfyltAvkortetMotKvote(uttaksgrad: Prosent): UttaksperiodeInfo {
check(uttaksgrad > Prosent.ZERO) { "Uttakgrad må være over 0 for delvis oppfylt, var $uttaksgrad" }
check(uttaksgrad < Prosent.valueOf(100)) { "Uttakgrad må være under 100% for delvis oppfylt, var $uttaksgrad" }
check(uttaksgrad == uttaksgrad.setScale(2, RoundingMode.UP)) { "Uttaksgrad skal være avrundet til 2 desimaler" }
return this.copy(
årsaker = setOf(Årsak.AVKORTET_MOT_KVOTE),
utfall = Utfall.OPPFYLT,
uttaksgrad = uttaksgrad,
utbetalingsgrader = this.utbetalingsgrader.map {
it.copy(
utbetalingsgrad = uttaksgrad
)
}
)
}

private fun RegelGrunnlag.finnForbrukteDagerHittil(): Pair<BigDecimal, LocalDate?> {
var antallDager = BigDecimal.ZERO
val relevantePerioder = mutableMapOf<LukketPeriode, UUID>()
Expand Down Expand Up @@ -165,12 +208,11 @@ private fun Map<LukketPeriode, UttaksperiodeInfo>.finnForbrukteDager(): Pair<Big
var antallDager = BigDecimal.ZERO
val relevantePerioder = mutableListOf<LukketPeriode>()

this.forEach { (annenPartsPeriode, info) ->
this.forEach { (periode, info) ->
if (info.utfall == Utfall.OPPFYLT) {
antallDager += (info.uttaksgrad.divide(HUNDRE_PROSENT, 2, RoundingMode.HALF_UP) * BigDecimal(
annenPartsPeriode.virkedager()
))
relevantePerioder.add(annenPartsPeriode)
val uttaksgrad = info.uttaksgrad.divide(HUNDRE_PROSENT, 2, RoundingMode.HALF_UP)
antallDager += uttaksgrad * BigDecimal(periode.virkedager())
relevantePerioder.add(periode)
}
}
return Pair(antallDager, relevantePerioder)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import no.nav.pleiepengerbarn.uttak.kontrakter.*
import no.nav.pleiepengerbarn.uttak.regler.*
import no.nav.pleiepengerbarn.uttak.regler.domene.RegelGrunnlag
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.math.BigDecimal
import java.time.Duration
Expand Down Expand Up @@ -91,6 +89,84 @@ class MaxAntallDagerRegelTest {
assertThat(resultatInfo2.utfall).isEqualTo(Utfall.IKKE_OPPFYLT)
}

@Test
internal fun `Søker har 50% uttak, får innvilget i 120 ukedager, deretter avslag`() {
//120 ukedager er 24 uker
//1.jan 2024 er en mandag

//24 hele uker fra og med 1.jan 2024 slutter 16.juni 2024 (en søndag)

val periode1 = LukketPeriode("2024-01-01/2024-07-01")
val søkersUttaksplan = Uttaksplan(
perioder = mapOf(
periode1 to dummyUttaksperiodeInfo(uttaksgrad = Prosent(50))
), trukketUttak = listOf()
)

val helePerioden = LukketPeriode(LocalDate.of(2024, Month.JANUARY, 1), LocalDate.of(2024, Month.JULY, 1))
val grunnlag = dummyRegelGrunnlag(helePerioden)

val resultat = regel.kjør(søkersUttaksplan, grunnlag)
assertThat(resultat.perioder).hasSize(2)
assertThat(resultat.kvoteInfo).isNotNull
assertThat(resultat.kvoteInfo!!.maxDato).isNull()
assertThat(resultat.kvoteInfo!!.totaltForbruktKvote).isEqualTo(BigDecimal.valueOf(60).setScale(2))

val resultatPeriode = resultat.perioder.keys.first()
val resultatInfo = resultat.perioder.values.first()
assertThat(resultatPeriode).isEqualTo(LukketPeriode("2024-01-01/2024-06-14"))
assertThat(resultatInfo.utfall).isEqualTo(Utfall.OPPFYLT)

val resultatPeriode2 = resultat.perioder.keys.last()
val resultatInfo2 = resultat.perioder.values.last()
assertThat(resultatPeriode2).isEqualTo(LukketPeriode("2024-06-15/2024-07-01"))
assertThat(resultatInfo2.utfall).isEqualTo(Utfall.IKKE_OPPFYLT)
}

@Test
internal fun `Søker får delvis innvilget den dagen det går tomt for kvote`() {
//120 ukedager er 24 uker
//1.jan 2024 er en mandag

//24 hele uker fra og med 1.jan 2024 slutter 16.juni 2024 (en søndag)

val søkersUttaksplan = Uttaksplan(
perioder = mapOf(
LukketPeriode("2024-01-01/2024-01-01") to dummyUttaksperiodeInfo(uttaksgrad = Prosent(49)),
LukketPeriode("2024-01-02/2024-07-01") to dummyUttaksperiodeInfo(uttaksgrad = Prosent(50))
), trukketUttak = listOf()
)

val helePerioden = LukketPeriode(LocalDate.of(2024, Month.JANUARY, 1), LocalDate.of(2024, Month.JULY, 1))
val grunnlag = dummyRegelGrunnlag(helePerioden)

val resultat = regel.kjør(søkersUttaksplan, grunnlag)
assertThat(resultat.perioder).hasSize(4)
assertThat(resultat.kvoteInfo).isNotNull
assertThat(resultat.kvoteInfo!!.maxDato).isNull()
assertThat(resultat.kvoteInfo!!.totaltForbruktKvote).isEqualTo(BigDecimal.valueOf(60).setScale(2))

val perioder = resultat.perioder.keys.toList()

assertThat(perioder[0]).isEqualTo(LukketPeriode("2024-01-01/2024-01-01"));
assertThat(resultat.perioder[perioder[0]]!!.utfall).isEqualTo(Utfall.OPPFYLT)
assertThat(resultat.perioder[perioder[0]]!!.uttaksgrad).isEqualTo(Prosent(49))

assertThat(perioder[1]).isEqualTo(LukketPeriode("2024-01-02/2024-06-16"));
assertThat(resultat.perioder[perioder[1]]!!.utfall).isEqualTo(Utfall.OPPFYLT)
assertThat(resultat.perioder[perioder[1]]!!.uttaksgrad).isEqualTo(Prosent(50))

assertThat(perioder[2]).isEqualTo(LukketPeriode("2024-06-17/2024-06-17"));
assertThat(resultat.perioder[perioder[2]]!!.utfall).isEqualTo(Utfall.OPPFYLT)
assertThat(resultat.perioder[perioder[2]]!!.årsaker).isEqualTo(setOf(Årsak.AVKORTET_MOT_KVOTE))
assertThat(resultat.perioder[perioder[2]]!!.uttaksgrad).isEqualByComparingTo(Prosent(1))

assertThat(perioder[3]).isEqualTo(LukketPeriode("2024-06-18/2024-07-01"));
assertThat(resultat.perioder[perioder[3]]!!.utfall).isEqualTo(Utfall.IKKE_OPPFYLT)
assertThat(resultat.perioder[perioder[3]]!!.uttaksgrad).isEqualByComparingTo(Prosent(0))
}


@Test
internal fun `Søker får avslått det fraværet som ikke er oppfylt av andre grunner, innvilget det som er innenfor kvoten, og avslått det som er over kvoten`() {
val periode1 = LukketPeriode("2020-01-06/2020-01-31") // 20 dager
Expand Down Expand Up @@ -476,10 +552,10 @@ private fun dummyRegelGrunnlagMedAndreParter(
)


private fun dummyUttaksperiodeInfo(oppgittTilsyn: Duration? = null, utfall: Utfall = Utfall.OPPFYLT) =
private fun dummyUttaksperiodeInfo(oppgittTilsyn: Duration? = null, utfall: Utfall = Utfall.OPPFYLT, uttaksgrad:Prosent = HUNDRE_PROSENT) =
UttaksperiodeInfo(
utfall = utfall,
utbetalingsgrader = mapOf(arbeidsforhold1 to Prosent(100)).somUtbetalingsgrader(),
utbetalingsgrader = mapOf(arbeidsforhold1 to uttaksgrad).somUtbetalingsgrader(),
annenPart = AnnenPart.ALENE,
beredskap = null,
nattevåk = null,
Expand All @@ -494,7 +570,7 @@ private fun dummyUttaksperiodeInfo(oppgittTilsyn: Duration? = null, utfall: Utfa
oppgittTilsyn = oppgittTilsyn,
pleiebehov = Pleiebehov.PROSENT_100.prosent,
søkersTapteArbeidstid = null,
uttaksgrad = HUNDRE_PROSENT,
uttaksgrad = uttaksgrad,
årsaker = setOf(),
uttaksgradUtenReduksjonGrunnetInntektsgradering = null,
uttaksgradMedReduksjonGrunnetInntektsgradering = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1986,15 +1986,15 @@ class UttakplanApiTest(@Autowired val restTemplate: TestRestTemplate) {

val uttakplan1søker1 = grunnlag1Søker1.opprettUttaksplan()
assertThat(uttakplan1søker1.kvoteInfo).isNotNull
assertThat(uttakplan1søker1.kvoteInfo!!.totaltForbruktKvote).isEqualTo(BigDecimal.valueOf(60.68).setScale(2))
assertThat(uttakplan1søker1.kvoteInfo!!.totaltForbruktKvote).isEqualTo(BigDecimal.valueOf(60).setScale(2))

val simuleringsresultat = grunnlag1Søker1.simulering()

assertThat(simuleringsresultat.uttakplanEndret).isFalse
}

@Test
internal fun `Livets sluttfase - Innvilger en hel dag når bruker egentlig har under en dag igjen, så kvoten kan gå over 60 dager`() {
internal fun `Livets sluttfase - Innvilger deler av dagen når bruker har under en dag igjen, så kvoten blir eksakt 60 dager`() {
val søknadsperiode = LukketPeriode("2022-11-30/2023-02-22")
val behandlingUUID1 = nesteBehandlingId()

Expand Down Expand Up @@ -2035,7 +2035,7 @@ class UttakplanApiTest(@Autowired val restTemplate: TestRestTemplate) {

val uttakplan1søker1 = grunnlag1Søker1.opprettUttaksplan()
assertThat(uttakplan1søker1.kvoteInfo).isNotNull
assertThat(uttakplan1søker1.kvoteInfo!!.totaltForbruktKvote).isEqualTo(BigDecimal.valueOf(60.39).setScale(2))
assertThat(uttakplan1søker1.kvoteInfo!!.totaltForbruktKvote).isEqualTo(BigDecimal.valueOf(60).setScale(2))

val simuleringsresultat = grunnlag1Søker1.simulering()

Expand Down