Skip to content

Commit

Permalink
dropper turnering av inntekter
Browse files Browse the repository at this point in the history
turneringen legger litt opp til at det kan skje mange ting, men strengt tatt er det jo kun "inntektsmelding vs. skatt" som kan skje.

SkattSykepengegrunnlag bør også refaktoreres litt, for akkurat nå representerer den både noe som kan ha inntekt og noe som er "ikke rapportert".
  • Loading branch information
davidsteinsland committed Nov 26, 2024
1 parent 9e0a1f3 commit 263d703
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 315 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@ package no.nav.helse.person.inntekt

import java.time.LocalDate
import java.time.LocalDateTime
import java.time.YearMonth
import java.util.UUID
import no.nav.helse.person.inntekt.AvklarbarSykepengegrunnlag.Inntektturnering
import no.nav.helse.økonomi.Inntekt
import kotlin.reflect.KClass

sealed class AvklarbarSykepengegrunnlag(
id: UUID,
Expand All @@ -15,56 +12,5 @@ sealed class AvklarbarSykepengegrunnlag(
beløp: Inntekt,
tidsstempel: LocalDateTime
) : Inntektsopplysning(id, hendelseId, dato, beløp, tidsstempel) {
protected abstract fun avklarSykepengegrunnlag(skjæringstidspunkt: LocalDate, førsteFraværsdag: LocalDate?): AvklarbarSykepengegrunnlag?

internal fun beste(other: AvklarbarSykepengegrunnlag): AvklarbarSykepengegrunnlag {
return turnering.avgjør(this, other)
}

private fun interface Inntektturnering {
fun beste(venstre: AvklarbarSykepengegrunnlag, høyre: AvklarbarSykepengegrunnlag): AvklarbarSykepengegrunnlag
}


internal companion object {

private val SisteAnkomne = Inntektturnering { venstre, høyre ->
when {
høyre.tidsstempel < venstre.tidsstempel -> venstre
else -> høyre
}
}
/*
gir venstre om måneden er tidligere enn høyre, høyre ellers;
eksempelvis dersom inntektsmelding (første fraværsdag) er i en annen måned enn skjæringstidspunktet, da
skal skatteopplysningene brukes
*/
private val TidligsteMåned = Inntektturnering { venstre, høyre ->
when {
YearMonth.from(venstre.dato) <= YearMonth.from(høyre.dato) -> venstre
else -> høyre
}
}
private val KunHøyre = Inntektturnering { _, høyre -> høyre }

private val turnering = mapOf(
Inntektsmelding::class to mapOf(
Inntektsmelding::class to SisteAnkomne,
SkattSykepengegrunnlag::class to TidligsteMåned,
IkkeRapportert::class to TidligsteMåned
)
)

private fun Map<KClass<Inntektsmelding>, Map<out KClass<out AvklarbarSykepengegrunnlag>, Inntektturnering>>.avgjør(venstre: AvklarbarSykepengegrunnlag, høyre: AvklarbarSykepengegrunnlag): AvklarbarSykepengegrunnlag {
return this[venstre::class]?.get(høyre::class)?.beste(venstre, høyre)
?: this[høyre::class]?.get(venstre::class)?.beste(høyre, venstre) // kommutativ variant; a+b = b+a
?: error("mangelfull inntektturnering for [${venstre::class.simpleName}, ${høyre::class.simpleName}]")
}
internal fun List<Inntektsmelding>?.avklarSykepengegrunnlag(skjæringstidspunkt: LocalDate, førsteFraværsdag: LocalDate?, skattSykepengegrunnlag: SkattSykepengegrunnlag?): Inntektsopplysning? {
val tilgjengelige = listOfNotNull(skattSykepengegrunnlag) + (this ?: emptyList())
val kandidater = tilgjengelige.mapNotNull { it.avklarSykepengegrunnlag(skjæringstidspunkt, førsteFraværsdag) }
if (kandidater.isEmpty()) return null
return kandidater.reduce { champion, challenger -> champion.beste(challenger) }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ internal class IkkeRapportert(
tidsstempel: LocalDateTime
) : AvklarbarSykepengegrunnlag(id, hendelseId, dato, Inntekt.INGEN, tidsstempel) {
internal constructor(dato: LocalDate, hendelseId: UUID, tidsstempel: LocalDateTime) : this(UUID.randomUUID(), hendelseId, dato, tidsstempel)
override fun avklarSykepengegrunnlag(skjæringstidspunkt: LocalDate, førsteFraværsdag: LocalDate?) =
takeIf { this.dato == skjæringstidspunkt }

override fun kanOverstyresAv(ny: Inntektsopplysning) = true

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ package no.nav.helse.person.inntekt
import java.time.LocalDate
import no.nav.helse.dto.deserialisering.InntektshistorikkInnDto
import no.nav.helse.dto.serialisering.InntektshistorikkUtDto
import no.nav.helse.person.inntekt.AvklarbarSykepengegrunnlag.Companion.avklarSykepengegrunnlag
import no.nav.helse.person.inntekt.Inntektsmelding.Companion.avklarSykepengegrunnlag

internal class Inntektshistorikk private constructor(private val historikk: MutableList<Inntektsmelding>) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import no.nav.helse.person.PersonObserver
import no.nav.helse.person.PersonObserver.Inntektsopplysningstype.INNTEKTSMELDING
import no.nav.helse.person.aktivitetslogg.IAktivitetslogg
import no.nav.helse.person.aktivitetslogg.Varselkode.RV_IV_7
import no.nav.helse.yearMonth
import no.nav.helse.økonomi.Inntekt

class Inntektsmelding internal constructor(
Expand All @@ -22,7 +23,7 @@ class Inntektsmelding internal constructor(
beløp: Inntekt,
tidsstempel: LocalDateTime,
private val kilde: Kilde
) : AvklarbarSykepengegrunnlag(id, hendelseId, dato, beløp, tidsstempel) {
) : Inntektsopplysning(id, hendelseId, dato, beløp, tidsstempel) {
internal constructor(dato: LocalDate, hendelseId: UUID, beløp: Inntekt, kilde: Kilde = Kilde.Arbeidsgiver, tidsstempel: LocalDateTime = LocalDateTime.now()) : this(UUID.randomUUID(), dato, hendelseId, beløp, tidsstempel, kilde)

override fun gjenbrukbarInntekt(beløp: Inntekt?) = beløp?.let { Inntektsmelding(dato, hendelseId, it, kilde, tidsstempel) }?: this
Expand All @@ -45,9 +46,8 @@ class Inntektsmelding internal constructor(
if (erOmregnetÅrsinntektEndret(this, gammel)) this
else gammel.overstyrer(this)

override fun avklarSykepengegrunnlag(skjæringstidspunkt: LocalDate, førsteFraværsdag: LocalDate?): AvklarbarSykepengegrunnlag? {
if (dato == skjæringstidspunkt) return this
if (førsteFraværsdag == null || dato != førsteFraværsdag) return null
internal fun avklarSykepengegrunnlag(skatt: AvklarbarSykepengegrunnlag): Inntektsopplysning {
if (skatt.dato.yearMonth < this.dato.yearMonth) return skatt
return this
}

Expand Down Expand Up @@ -135,6 +135,17 @@ class Inntektsmelding internal constructor(
kilde = Kilde.gjenopprett(dto.kilde),
)
}

internal fun List<Inntektsmelding>.finnInntektsmeldingForSkjæringstidspunkt(skjæringstidspunkt: LocalDate, førsteFraværsdag: LocalDate?): Inntektsmelding? {
val inntektsmeldinger = this.filter { it.dato == skjæringstidspunkt || it.dato == førsteFraværsdag }
return inntektsmeldinger.maxByOrNull { inntektsmelding -> inntektsmelding.tidsstempel }
}

internal fun List<Inntektsmelding>.avklarSykepengegrunnlag(skjæringstidspunkt: LocalDate, førsteFraværsdag: LocalDate?, skattSykepengegrunnlag: SkattSykepengegrunnlag?): Inntektsopplysning? {
val inntektsmelding = finnInntektsmeldingForSkjæringstidspunkt(skjæringstidspunkt, førsteFraværsdag)
val skatt = skattSykepengegrunnlag?.takeIf { it.kanBrukes(skjæringstidspunkt) }?.somSykepengegrunnlag() ?: return inntektsmelding
return inntektsmelding?.avklarSykepengegrunnlag(skatt) ?: skatt
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package no.nav.helse.person.inntekt

import java.time.LocalDate
import java.time.LocalDateTime
import java.util.UUID
import no.nav.helse.dto.AnsattPeriodeDto
import no.nav.helse.dto.deserialisering.InntektsopplysningInnDto
import no.nav.helse.dto.serialisering.InntektsopplysningUtDto
Expand All @@ -14,6 +11,9 @@ import no.nav.helse.person.inntekt.AnsattPeriode.Companion.harArbeidsforholdNyer
import no.nav.helse.person.inntekt.Skatteopplysning.Companion.sisteMåneder
import no.nav.helse.person.inntekt.Skatteopplysning.Companion.subsumsjonsformat
import no.nav.helse.økonomi.Inntekt
import java.time.LocalDate
import java.time.LocalDateTime
import java.util.*

internal class SkattSykepengegrunnlag private constructor(
id: UUID,
Expand Down Expand Up @@ -56,25 +56,24 @@ internal class SkattSykepengegrunnlag private constructor(
tidsstempel: LocalDateTime = LocalDateTime.now()
) : this(UUID.randomUUID(), hendelseId, dato, Skatteopplysning.sisteTreMåneder(dato, inntektsopplysninger), ansattPerioder, tidsstempel)

override fun avklarSykepengegrunnlag(skjæringstidspunkt: LocalDate, førsteFraværsdag: LocalDate?): AvklarbarSykepengegrunnlag? {
if (this.dato != skjæringstidspunkt) return null
if (ansattPerioder.isEmpty()) return null
fun kanBrukes(skjæringstidspunkt: LocalDate): Boolean {
if (this.dato != skjæringstidspunkt) return false
if (ansattPerioder.isEmpty()) return false
// ser bort fra skatteinntekter om man ikke er ansatt på skjæringstidspunktet:
if (!ansattVedSkjæringstidspunkt(skjæringstidspunkt)) return null
// bruker skatteinntekter om det foreligger inntekter innenfor 2 mnd fra skjæringstidspunktet:
if (sisteMåneder(skjæringstidspunkt, MAKS_INNTEKT_GAP, inntektsopplysninger).isNotEmpty()) return this
// ser bort fra skatteinntekter om man er ansatt på skjæringstidspunktet, men inntektene er eldre enn 2 mnd fra skjæringstidspunktet (avsluttet arb.forhold?):
if (inntektsopplysninger.isNotEmpty()) return null
// ser bort fra skatteinntekter om arb.forholdet er eldre enn 2 mnd fra skjæringstidspunktet:
if (!nyoppstartetArbeidsforhold(skjæringstidspunkt)) return null
// nyoppstartet arbeidsforhold (startdato innen 2 mnd fra skjæringstidspunktet), og ingen inntekter foreligger:
// todo bare returnere "this" og mappe ut IKKE_RAPPORTERT i SpeilBuilder?
return IkkeRapportert(
if (!ansattVedSkjæringstidspunkt(skjæringstidspunkt)) return false
if (inntektsopplysninger.isEmpty()) {
return nyoppstartetArbeidsforhold(skjæringstidspunkt)
}
return sisteMåneder(skjæringstidspunkt, MAKS_INNTEKT_GAP, inntektsopplysninger).isNotEmpty()
}

internal fun somSykepengegrunnlag() =
if (!inntektsopplysninger.isEmpty()) this
else IkkeRapportert(
hendelseId = this.hendelseId,
dato = this.dato,
tidsstempel = this.tidsstempel
)
}

private fun nyoppstartetArbeidsforhold(skjæringstidspunkt: LocalDate) =
ansattPerioder.harArbeidsforholdNyereEnn(skjæringstidspunkt, MAKS_INNTEKT_GAP)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package no.nav.helse.person.inntekt

import java.time.LocalDate
import java.util.UUID
import no.nav.helse.august
import no.nav.helse.desember
import no.nav.helse.dsl.ArbeidsgiverHendelsefabrikk
import no.nav.helse.etterlevelse.Subsumsjonslogg.Companion.EmptyLog
import no.nav.helse.februar
Expand All @@ -13,33 +9,23 @@ import no.nav.helse.hendelser.til
import no.nav.helse.inspectors.InntektshistorikkInspektør
import no.nav.helse.inspectors.inspektør
import no.nav.helse.januar
import no.nav.helse.november
import no.nav.helse.oktober
import no.nav.helse.person.aktivitetslogg.Aktivitetslogg
import no.nav.helse.testhelpers.assertNotNull
import no.nav.helse.testhelpers.inntektperioderForSykepengegrunnlag
import no.nav.helse.testhelpers.resetSeed
import no.nav.helse.økonomi.Inntekt
import no.nav.helse.økonomi.Inntekt.Companion.INGEN
import no.nav.helse.økonomi.Inntekt.Companion.daglig
import no.nav.helse.økonomi.Inntekt.Companion.månedlig
import no.nav.helse.økonomi.Inntekt.Companion.årlig
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import java.time.LocalDate
import java.util.*

internal class InntektshistorikkTest {

private lateinit var historikk: Inntektshistorikk
private val inspektør get() = InntektshistorikkInspektør(historikk.view())

private companion object {
const val UNG_PERSON_FNR_2018 = "12029240045"
val UNG_PERSON_FØDSELSDATO = 12.februar(1992)

const val AKTØRID = "42"
const val ORGNUMMER = "987654321"
val INNTEKT = 31000.00.månedlig
val hendelsefabrikk = ArbeidsgiverHendelsefabrikk(
Expand All @@ -53,40 +39,6 @@ internal class InntektshistorikkTest {
historikk = Inntektshistorikk()
}

@Test
fun `sykepengegrunnlag for arbeidsgiver med nytt arbeidsforhold`() {
val opplysning = historikk.avklarSykepengegrunnlag(
1.februar,
1.februar,
SkattSykepengegrunnlag(
hendelseId = UUID.randomUUID(),
dato = 1.februar,
inntektsopplysninger = emptyList(),
ansattPerioder = listOf(AnsattPeriode(1.januar, null))
)
)
assertNotNull(opplysning)
assertEquals(IkkeRapportert::class, opplysning::class)
assertEquals(INGEN, opplysning.inspektør.beløp)
}

@Test
fun `sykepengegrunnlag for arbeidsgiver med nytt deaktivert arbeidsforhold`() {
val opplysning = historikk.avklarSykepengegrunnlag(
1.februar,
1.februar,
SkattSykepengegrunnlag(
hendelseId = UUID.randomUUID(),
dato = 1.februar,
inntektsopplysninger = emptyList(),
ansattPerioder = listOf(AnsattPeriode(1.januar, null))
)
)
assertNotNull(opplysning)
assertEquals(IkkeRapportert::class, opplysning::class)
assertEquals(INGEN, opplysning.inspektør.beløp)
}

@Test
fun `Inntekt fra inntektsmelding brukes til å beregne sykepengegrunnlaget`() {
inntektsmelding(førsteFraværsdag = 1.januar).addInntekt(historikk, EmptyLog)
Expand Down Expand Up @@ -115,97 +67,6 @@ internal class InntektshistorikkTest {
assertNull(historikk.avklarSykepengegrunnlag(2.januar, 2.januar, null))
}

@Test
fun `intrikat test for sykepengegrunnlag der første fraværsdag er 31 desember`() {
val skattSykepengegrunnlag = inntektperioderForSykepengegrunnlag {
1.desember(2016) til 1.desember(2016) inntekter {
ORGNUMMER inntekt 10000
}
1.desember(2016) til 1.august(2017) inntekter {
ORGNUMMER inntekt 20000
}
1.oktober(2017) til 1.oktober(2017) inntekter {
ORGNUMMER inntekt 30000
}
1.november(2017) til 1.januar inntekter {
ORGNUMMER inntekt 12000
ORGNUMMER inntekt 22000
}
}
.map { it.tilSykepengegrunnlag(31.desember(2017), UUID.randomUUID()) }
.single() + SkattSykepengegrunnlag(UUID.randomUUID(), 1.desember, emptyList(), listOf(AnsattPeriode(1.november(2017), null)))

assertEquals(256000.årlig, historikk.avklarSykepengegrunnlag(31.desember(2017), 31.desember(2017), skattSykepengegrunnlag)?.inspektør?.beløp)
}

@Test
fun `intrikat test for sykepengegrunnlag der første fraværsdag er 1 januar`() {
val skattSykepengegrunnlag = inntektperioderForSykepengegrunnlag {
1.desember(2016) til 1.desember(2016) inntekter {
ORGNUMMER inntekt 10000
}
1.desember(2016) til 1.august(2017) inntekter {
ORGNUMMER inntekt 20000
}
1.oktober(2017) til 1.oktober(2017) inntekter {
ORGNUMMER inntekt 30000
}
1.november(2017) til 1.januar inntekter {
ORGNUMMER inntekt 12000
ORGNUMMER inntekt 22000
}
}
.map { it.tilSykepengegrunnlag(1.januar, UUID.randomUUID()) }
.single() + SkattSykepengegrunnlag(UUID.randomUUID(), 1.desember, emptyList(), listOf(AnsattPeriode(1.desember(2016), null)))
assertEquals(392000.årlig, historikk.avklarSykepengegrunnlag(1.januar, 1.januar, skattSykepengegrunnlag)?.inspektør?.beløp)
}

@Test
fun `Inntekt fra skatt siste tre måneder brukes til å beregne sykepengegrunnlaget`() {
val skattSykepengegrunnlag = inntektperioderForSykepengegrunnlag {
1.desember(2016) til 1.desember(2017) inntekter {
ORGNUMMER inntekt INNTEKT
}
1.desember(2016) til 1.august(2017) inntekter {
ORGNUMMER inntekt INNTEKT
}
}
.map { it.tilSykepengegrunnlag(1.januar, UUID.randomUUID()) }
.single() + SkattSykepengegrunnlag(UUID.randomUUID(), 1.desember, emptyList(), listOf(AnsattPeriode(1.desember(2016), null)))
assertEquals(INNTEKT, historikk.avklarSykepengegrunnlag(1.januar, 1.januar, skattSykepengegrunnlag)?.inspektør?.beløp)
}

@Test
fun `Inntekt fra skatt siste tre måneder som tilsammen er et negativt beløp`() {
val skattSykepengegrunnlag = inntektperioderForSykepengegrunnlag {
1.oktober(2017) til 1.desember(2017) inntekter {
ORGNUMMER inntekt INNTEKT * -1
}
}
.map { it.tilSykepengegrunnlag(1.januar, UUID.randomUUID()) }
.single() + SkattSykepengegrunnlag(UUID.randomUUID(), 1.desember, emptyList(), listOf(AnsattPeriode(1.oktober(2017), null)))
val inntektsopplysning = historikk.avklarSykepengegrunnlag(1.januar, 1.januar, skattSykepengegrunnlag)
assertTrue(inntektsopplysning is SkattSykepengegrunnlag)
assertEquals(INGEN, inntektsopplysning?.inspektør?.beløp)
}

@Test
fun `Inntekt fra skatt er minst 0 kroner`() {
val skattComposite = SkattSykepengegrunnlag(
UUID.randomUUID(), 1.januar, inntektsopplysninger = listOf(
Skatteopplysning(
hendelseId = UUID.randomUUID(),
beløp = (-2500).daglig,
måned = desember(2017),
type = Skatteopplysning.Inntekttype.LØNNSINNTEKT,
fordel = "fordel",
beskrivelse = "beskrivelse"
),
), emptyList()
)
assertEquals(INGEN, skattComposite.inspektør.beløp)
}

@Test
fun `Inntekt for annen dato og samme kilde erstatter ikke eksisterende`() {
inntektsmelding(førsteFraværsdag = 1.januar).addInntekt(historikk, EmptyLog)
Expand Down
Loading

0 comments on commit 263d703

Please sign in to comment.